]> git.ipfire.org Git - thirdparty/cups.git/blame - cups/language.c
Merge changes from 1.4svn-r7067.
[thirdparty/cups.git] / cups / language.c
CommitLineData
ef416fc2 1/*
2e4ff8af 2 * "$Id: language.c 6916 2007-09-05 21:14:08Z mike $"
ef416fc2 3 *
4 * I18N/language support for the Common UNIX Printing System (CUPS).
5 *
bc44d920 6 * Copyright 2007 by Apple Inc.
b86bc4cf 7 * Copyright 1997-2007 by Easy Software Products.
ef416fc2 8 *
9 * These coded instructions, statements, and computer programs are the
bc44d920 10 * property of Apple Inc. and are protected by Federal copyright
11 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
12 * which should have been included with this file. If this file is
13 * file is missing or damaged, see the license at "http://www.cups.org/".
ef416fc2 14 *
15 * This file is subject to the Apple OS-Developed Software exception.
16 *
17 * Contents:
18 *
19 * _cupsEncodingName() - Return the character encoding name string
20 * for the given encoding enumeration.
21 * cupsLangDefault() - Return the default language.
22 * cupsLangEncoding() - Return the character encoding (us-ascii, etc.)
23 * for the given language.
24 * cupsLangFlush() - Flush all language data out of the cache.
25 * cupsLangFree() - Free language data.
26 * cupsLangGet() - Get a language.
27 * _cupsLangString() - Get a message string.
28 * _cupsMessageFree() - Free a messages array.
29 * _cupsMessageLoad() - Load a .po file into a messages array.
30 * _cupsMessageLookup() - Lookup a message string.
ef416fc2 31 * appleLangDefault() - Get the default locale string.
32 * cups_cache_lookup() - Lookup a language in the cache...
33 * cups_message_compare() - Compare two messages.
34 * cups_unquote() - Unquote characters in strings...
35 */
36
37/*
38 * Include necessary headers...
39 */
40
41#include "globals.h"
42#include "debug.h"
43#include <stdlib.h>
bd7854cb 44#include <errno.h>
ef416fc2 45#ifdef HAVE_LANGINFO_H
46# include <langinfo.h>
47#endif /* HAVE_LANGINFO_H */
48#ifdef WIN32
49# include <io.h>
50#else
51# include <unistd.h>
52#endif /* WIN32 */
fa73b229 53#ifdef HAVE_COREFOUNDATION_H
54# include <CoreFoundation/CoreFoundation.h>
55#endif /* HAVE_COREFOUNDATION_H */
ef416fc2 56
57
d6ae789d 58/*
59 * Local globals...
60 */
61
62#ifdef HAVE_PTHREAD_H
63static pthread_mutex_t lang_mutex = PTHREAD_MUTEX_INITIALIZER;
64 /* Mutex to control access to cache */
65#endif /* HAVE_PTHREAD_H */
66static cups_lang_t *lang_cache = NULL;
67 /* Language string cache */
68
69
ef416fc2 70/*
71 * Local functions...
72 */
73
74#ifdef __APPLE__
ef416fc2 75static const char *appleLangDefault(void);
76#endif /* __APPLE__ */
77static cups_lang_t *cups_cache_lookup(const char *name,
78 cups_encoding_t encoding);
79static int cups_message_compare(_cups_message_t *m1,
80 _cups_message_t *m2);
81static void cups_unquote(char *d, const char *s);
82
83
84/*
85 * Local globals...
86 */
87
88static const char * const lang_encodings[] =
89 { /* Encoding strings */
90 "us-ascii", "iso-8859-1",
91 "iso-8859-2", "iso-8859-3",
92 "iso-8859-4", "iso-8859-5",
93 "iso-8859-6", "iso-8859-7",
94 "iso-8859-8", "iso-8859-9",
95 "iso-8859-10", "utf-8",
96 "iso-8859-13", "iso-8859-14",
97 "iso-8859-15", "windows-874",
98 "windows-1250", "windows-1251",
99 "windows-1252", "windows-1253",
100 "windows-1254", "windows-1255",
101 "windows-1256", "windows-1257",
102 "windows-1258", "koi8-r",
103 "koi8-u", "iso-8859-11",
bd7854cb 104 "iso-8859-16", "mac-roman",
ef416fc2 105 "unknown", "unknown",
106 "unknown", "unknown",
107 "unknown", "unknown",
108 "unknown", "unknown",
109 "unknown", "unknown",
110 "unknown", "unknown",
111 "unknown", "unknown",
112 "unknown", "unknown",
113 "unknown", "unknown",
114 "unknown", "unknown",
115 "unknown", "unknown",
116 "unknown", "unknown",
117 "unknown", "unknown",
118 "unknown", "unknown",
119 "unknown", "unknown",
120 "unknown", "unknown",
121 "unknown", "unknown",
122 "windows-932", "windows-936",
123 "windows-949", "windows-950",
124 "windows-1361", "unknown",
125 "unknown", "unknown",
126 "unknown", "unknown",
127 "unknown", "unknown",
128 "unknown", "unknown",
129 "unknown", "unknown",
130 "unknown", "unknown",
131 "unknown", "unknown",
132 "unknown", "unknown",
133 "unknown", "unknown",
134 "unknown", "unknown",
135 "unknown", "unknown",
136 "unknown", "unknown",
137 "unknown", "unknown",
138 "unknown", "unknown",
139 "unknown", "unknown",
140 "unknown", "unknown",
141 "unknown", "unknown",
142 "unknown", "unknown",
143 "unknown", "unknown",
144 "unknown", "unknown",
145 "unknown", "unknown",
146 "unknown", "unknown",
147 "unknown", "unknown",
148 "unknown", "unknown",
149 "unknown", "unknown",
150 "unknown", "unknown",
151 "unknown", "unknown",
152 "unknown", "unknown",
153 "unknown", "unknown",
154 "euc-cn", "euc-jp",
155 "euc-kr", "euc-tw"
156 };
157
158
159/*
160 * '_cupsEncodingName()' - Return the character encoding name string
161 * for the given encoding enumeration.
162 */
163
164const char * /* O - Character encoding */
165_cupsEncodingName(
166 cups_encoding_t encoding) /* I - Encoding value */
167{
168 if (encoding < 0 ||
169 encoding >= (sizeof(lang_encodings) / sizeof(const char *)))
170 return (lang_encodings[0]);
171 else
172 return (lang_encodings[encoding]);
173}
174
175
176/*
177 * 'cupsLangDefault()' - Return the default language.
178 */
179
180cups_lang_t * /* O - Language data */
181cupsLangDefault(void)
182{
183 return (cupsLangGet(NULL));
184}
185
186
187/*
188 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
189 * for the given language.
190 */
191
192const char * /* O - Character encoding */
193cupsLangEncoding(cups_lang_t *lang) /* I - Language data */
194{
195 if (lang == NULL)
196 return ((char*)lang_encodings[0]);
197 else
198 return ((char*)lang_encodings[lang->encoding]);
199}
200
201
202/*
203 * 'cupsLangFlush()' - Flush all language data out of the cache.
204 */
205
206void
207cupsLangFlush(void)
208{
209 cups_lang_t *lang, /* Current language */
210 *next; /* Next language */
ef416fc2 211
212
213 /*
214 * Free all languages in the cache...
215 */
216
d6ae789d 217#ifdef HAVE_PTHREAD_H
218 pthread_mutex_lock(&lang_mutex);
219#endif /* HAVE_PTHREAD_H */
220
221 for (lang = lang_cache; lang != NULL; lang = next)
ef416fc2 222 {
223 /*
224 * Free all messages...
225 */
226
227 _cupsMessageFree(lang->strings);
228
229 /*
230 * Then free the language structure itself...
231 */
232
233 next = lang->next;
234 free(lang);
235 }
236
d6ae789d 237 lang_cache = NULL;
238
239#ifdef HAVE_PTHREAD_H
240 pthread_mutex_unlock(&lang_mutex);
241#endif /* HAVE_PTHREAD_H */
ef416fc2 242}
243
244
245/*
246 * 'cupsLangFree()' - Free language data.
247 *
248 * This does not actually free anything; use cupsLangFlush() for that.
249 */
250
251void
252cupsLangFree(cups_lang_t *lang) /* I - Language to free */
253{
d6ae789d 254#ifdef HAVE_PTHREAD_H
255 pthread_mutex_lock(&lang_mutex);
256#endif /* HAVE_PTHREAD_H */
257
ef416fc2 258 if (lang != NULL && lang->used > 0)
259 lang->used --;
d6ae789d 260
261#ifdef HAVE_PTHREAD_H
262 pthread_mutex_unlock(&lang_mutex);
263#endif /* HAVE_PTHREAD_H */
ef416fc2 264}
265
266
267/*
268 * 'cupsLangGet()' - Get a language.
269 */
270
271cups_lang_t * /* O - Language data */
272cupsLangGet(const char *language) /* I - Language or locale */
273{
274 int i; /* Looping var */
480ef0fe 275#ifndef __APPLE__
276 char locale[255]; /* Copy of locale name */
277#endif /* !__APPLE__ */
278 char langname[16], /* Requested language name */
ef416fc2 279 country[16], /* Country code */
280 charset[16], /* Character set */
ef416fc2 281 *csptr, /* Pointer to CODESET string */
ef416fc2 282 *ptr, /* Pointer into language/charset */
283 real[48], /* Real language name */
284 filename[1024]; /* Filename for language locale file */
285 cups_encoding_t encoding; /* Encoding to use */
286 cups_lang_t *lang; /* Current language... */
ef416fc2 287 _cups_globals_t *cg = _cupsGlobals();
288 /* Pointer to library globals */
289 static const char * const locale_encodings[] =
290 { /* Locale charset names */
291 "ASCII", "ISO88591", "ISO88592", "ISO88593",
292 "ISO88594", "ISO88595", "ISO88596", "ISO88597",
293 "ISO88598", "ISO88599", "ISO885910", "UTF8",
294 "ISO885913", "ISO885914", "ISO885915", "CP874",
295 "CP1250", "CP1251", "CP1252", "CP1253",
296 "CP1254", "CP1255", "CP1256", "CP1257",
297 "CP1258", "KOI8R", "KOI8U", "ISO885911",
bd7854cb 298 "ISO885916", "MACROMAN", "", "",
ef416fc2 299
300 "", "", "", "",
301 "", "", "", "",
302 "", "", "", "",
303 "", "", "", "",
304 "", "", "", "",
305 "", "", "", "",
306 "", "", "", "",
307 "", "", "", "",
308
309 "CP932", "CP936", "CP949", "CP950",
310 "CP1361", "", "", "",
311 "", "", "", "",
312 "", "", "", "",
313 "", "", "", "",
314 "", "", "", "",
315 "", "", "", "",
316 "", "", "", "",
317
318 "", "", "", "",
319 "", "", "", "",
320 "", "", "", "",
321 "", "", "", "",
322 "", "", "", "",
323 "", "", "", "",
324 "", "", "", "",
325 "", "", "", "",
326
327 "EUCCN", "EUCJP", "EUCKR", "EUCTW"
328 };
329
330
331 DEBUG_printf(("cupsLangGet(language=\"%s\")\n", language ? language : "(null)"));
332
333#ifdef __APPLE__
757d2cad 334 /*
335 * Set the character set to UTF-8...
336 */
337
338 strcpy(charset, "UTF8");
339
ef416fc2 340 /*
341 * Apple's setlocale doesn't give us the user's localization
342 * preference so we have to look it up this way...
343 */
344
3d8365b8 345 if (!language && (language = getenv("LANG")) == NULL)
ef416fc2 346 language = appleLangDefault();
757d2cad 347
ef416fc2 348#else
757d2cad 349 /*
350 * Set the charset to "unknown"...
351 */
352
353 charset[0] = '\0';
354
355 /*
356 * Use setlocale() to determine the currently set locale, and then
357 * fallback to environment variables to avoid setting the locale,
358 * since setlocale() is not thread-safe!
359 */
360
361 if (!language)
ef416fc2 362 {
363 /*
364 * First see if the locale has been set; if it is still "C" or
757d2cad 365 * "POSIX", use the environment to get the default...
ef416fc2 366 */
367
368# ifdef LC_MESSAGES
369 ptr = setlocale(LC_MESSAGES, NULL);
370# else
371 ptr = setlocale(LC_ALL, NULL);
372# endif /* LC_MESSAGES */
373
374 DEBUG_printf(("cupsLangGet: current locale is \"%s\"\n",
375 ptr ? ptr : "(null)"));
376
377 if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
ef416fc2 378 {
757d2cad 379 /*
380 * Get the character set from the LC_CTYPE locale setting...
381 */
382
383 if ((ptr = getenv("LC_CTYPE")) == NULL)
384 if ((ptr = getenv("LC_ALL")) == NULL)
385 if ((ptr = getenv("LANG")) == NULL)
386 ptr = "en_US";
387
388 if ((csptr = strchr(ptr, '.')) != NULL)
389 {
390 /*
391 * Extract the character set from the environment...
392 */
393
394 for (ptr = charset, csptr ++; *csptr; csptr ++)
395 if (ptr < (charset + sizeof(charset) - 1) && isalnum(*csptr & 255))
396 *ptr++ = *csptr;
397
398 *ptr = '\0';
399 }
757d2cad 400
401 /*
402 * Get the locale for messages from the LC_MESSAGES locale setting...
403 */
404
405 if ((ptr = getenv("LC_MESSAGES")) == NULL)
406 if ((ptr = getenv("LC_ALL")) == NULL)
407 if ((ptr = getenv("LANG")) == NULL)
408 ptr = "en_US";
ef416fc2 409 }
ef416fc2 410
411 if (ptr)
412 {
413 strlcpy(locale, ptr, sizeof(locale));
414 language = locale;
415
a41f09e2
MS
416 /*
417 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
418 */
419
420 if (!strncmp(locale, "nb", 2))
421 locale[1] = 'o';
422
423 DEBUG_printf(("cupsLangGet: new language value is \"%s\"\n", language));
ef416fc2 424 }
425 }
426#endif /* __APPLE__ */
427
428 /*
429 * If "language" is NULL at this point, then chances are we are using
430 * a language that is not installed for the base OS.
431 */
432
433 if (!language)
434 {
435 /*
757d2cad 436 * Switch to the POSIX ("C") locale...
ef416fc2 437 */
438
757d2cad 439 language = "C";
ef416fc2 440 }
441
ef416fc2 442#ifdef CODESET
443 /*
444 * On systems that support the nl_langinfo(CODESET) call, use
445 * this value as the character set...
446 */
447
757d2cad 448 if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
ef416fc2 449 {
450 /*
451 * Copy all of the letters and numbers in the CODESET string...
452 */
453
454 for (ptr = charset; *csptr; csptr ++)
455 if (isalnum(*csptr & 255) && ptr < (charset + sizeof(charset) - 1))
456 *ptr++ = *csptr;
457
458 *ptr = '\0';
459
460 DEBUG_printf(("cupsLangGet: charset set to \"%s\" via nl_langinfo(CODESET)...\n",
461 charset));
462 }
463#endif /* CODESET */
464
07725fee 465 /*
466 * If we don't have a character set by now, default to UTF-8...
467 */
468
469 if (!charset[0])
470 strcpy(charset, "UTF8");
471
ef416fc2 472 /*
473 * Parse the language string passed in to a locale string. "C" is the
474 * standard POSIX locale and is copied unchanged. Otherwise the
475 * language string is converted from ll-cc[.charset] (language-country)
476 * to ll_CC[.CHARSET] to match the file naming convention used by all
477 * POSIX-compliant operating systems. Invalid language names are mapped
478 * to the POSIX locale.
479 */
480
481 country[0] = '\0';
482
483 if (language == NULL || !language[0] ||
484 !strcmp(language, "POSIX"))
485 strcpy(langname, "C");
486 else
487 {
488 /*
489 * Copy the parts of the locale string over safely...
490 */
491
492 for (ptr = langname; *language; language ++)
493 if (*language == '_' || *language == '-' || *language == '.')
494 break;
495 else if (ptr < (langname + sizeof(langname) - 1))
496 *ptr++ = tolower(*language & 255);
497
498 *ptr = '\0';
499
500 if (*language == '_' || *language == '-')
501 {
502 /*
503 * Copy the country code...
504 */
505
506 for (language ++, ptr = country; *language; language ++)
507 if (*language == '.')
508 break;
509 else if (ptr < (country + sizeof(country) - 1))
510 *ptr++ = toupper(*language & 255);
511
512 *ptr = '\0';
513 }
514
515 if (*language == '.' && !charset[0])
516 {
517 /*
518 * Copy the encoding...
519 */
520
521 for (language ++, ptr = charset; *language; language ++)
522 if (isalnum(*language & 255) && ptr < (charset + sizeof(charset) - 1))
523 *ptr++ = toupper(*language & 255);
524
525 *ptr = '\0';
526 }
527
528 /*
529 * Force a POSIX locale for an invalid language name...
530 */
531
532 if (strlen(langname) != 2)
533 {
534 strcpy(langname, "C");
535 country[0] = '\0';
536 charset[0] = '\0';
537 }
538 }
539
ef416fc2 540 DEBUG_printf(("cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"\n",
541 langname, country, charset));
542
543 /*
544 * Figure out the desired encoding...
545 */
546
547 encoding = CUPS_AUTO_ENCODING;
548
549 if (charset[0])
550 {
bd7854cb 551 for (i = 0;
552 i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
553 i ++)
ef416fc2 554 if (!strcasecmp(charset, locale_encodings[i]))
555 {
556 encoding = (cups_encoding_t)i;
557 break;
558 }
8ca02f3c 559
560 if (encoding == CUPS_AUTO_ENCODING)
561 {
562 /*
563 * Map alternate names for various character sets...
564 */
565
566 if (!strcasecmp(charset, "iso-2022-jp") ||
567 !strcasecmp(charset, "sjis"))
568 encoding = CUPS_WINDOWS_932;
569 else if (!strcasecmp(charset, "iso-2022-cn"))
570 encoding = CUPS_WINDOWS_936;
571 else if (!strcasecmp(charset, "iso-2022-kr"))
572 encoding = CUPS_WINDOWS_949;
573 else if (!strcasecmp(charset, "big5"))
574 encoding = CUPS_WINDOWS_950;
575 }
ef416fc2 576 }
577
578 DEBUG_printf(("cupsLangGet: encoding=%d(%s)\n", encoding,
579 encoding == CUPS_AUTO_ENCODING ? "auto" :
580 lang_encodings[encoding]));
581
582 /*
583 * See if we already have this language/country loaded...
584 */
585
bd7854cb 586 if (country[0])
587 {
588 snprintf(real, sizeof(real), "%s_%s", langname, country);
ef416fc2 589
bd7854cb 590 snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
591 real, real);
592 }
593 else
d6ae789d 594 {
595 strcpy(real, langname);
bd7854cb 596 filename[0] = '\0'; /* anti-compiler-warning-code */
d6ae789d 597 }
598
599#ifdef HAVE_PTHREAD_H
600 pthread_mutex_lock(&lang_mutex);
601#endif /* HAVE_PTHREAD_H */
602
c0e1af83 603 if ((lang = cups_cache_lookup(real, encoding)) != NULL)
d6ae789d 604 {
605#ifdef HAVE_PTHREAD_H
606 pthread_mutex_unlock(&lang_mutex);
607#endif /* HAVE_PTHREAD_H */
608
609 return (lang);
610 }
ef416fc2 611
612 if (!country[0] || access(filename, 0))
613 {
614 /*
615 * Country localization not available, look for generic localization...
616 */
617
bd7854cb 618 snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
ef416fc2 619 langname, langname);
620
621 if (access(filename, 0))
622 {
623 /*
624 * No generic localization, so use POSIX...
625 */
626
bd7854cb 627 DEBUG_printf(("access(\"%s\", 0): %s\n", filename, strerror(errno)));
628
bd7854cb 629 snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir);
ef416fc2 630 }
ef416fc2 631 }
632
633 /*
634 * See if there is a free language available; if so, use that
635 * record...
636 */
637
d6ae789d 638 for (lang = lang_cache; lang != NULL; lang = lang->next)
ef416fc2 639 if (lang->used == 0)
640 break;
641
642 if (lang == NULL)
643 {
644 /*
645 * Allocate memory for the language and add it to the cache.
646 */
647
648 if ((lang = calloc(sizeof(cups_lang_t), 1)) == NULL)
d6ae789d 649 {
650#ifdef HAVE_PTHREAD_H
651 pthread_mutex_unlock(&lang_mutex);
652#endif /* HAVE_PTHREAD_H */
653
ef416fc2 654 return (NULL);
d6ae789d 655 }
ef416fc2 656
d6ae789d 657 lang->next = lang_cache;
658 lang_cache = lang;
ef416fc2 659 }
bd7854cb 660 else
661 {
662 /*
663 * Free all old strings as needed...
664 */
ef416fc2 665
bd7854cb 666 _cupsMessageFree(lang->strings);
667 }
ef416fc2 668
669 /*
670 * Then assign the language and encoding fields...
671 */
672
673 lang->used ++;
674 strlcpy(lang->language, real, sizeof(lang->language));
675
676 if (encoding != CUPS_AUTO_ENCODING)
677 lang->encoding = encoding;
678 else
679 lang->encoding = CUPS_UTF8;
680
681 /*
682 * Read the strings from the file...
683 */
684
685 lang->strings = _cupsMessageLoad(filename);
686
687 /*
688 * Return...
689 */
690
d6ae789d 691#ifdef HAVE_PTHREAD_H
692 pthread_mutex_unlock(&lang_mutex);
693#endif /* HAVE_PTHREAD_H */
694
ef416fc2 695 return (lang);
696}
697
698
699/*
700 * '_cupsLangString()' - Get a message string.
701 *
702 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
703 * convert the string to the language encoding.
704 */
705
706const char * /* O - Localized message */
707_cupsLangString(cups_lang_t *lang, /* I - Language */
708 const char *message) /* I - Message */
709{
710 /*
711 * Range check input...
712 */
713
714 if (!lang || !message)
715 return (message);
716
d6ae789d 717#ifdef HAVE_PTHREAD_H
718 {
719 const char *s; /* Localized message */
720
721 pthread_mutex_lock(&lang_mutex);
722
723 s = _cupsMessageLookup(lang->strings, message);
724
725 pthread_mutex_unlock(&lang_mutex);
726
727 return (s);
728 }
729#else
ef416fc2 730 return (_cupsMessageLookup(lang->strings, message));
d6ae789d 731#endif /* HAVE_PTHREAD_H */
ef416fc2 732}
733
734
735/*
736 * '_cupsMessageFree()' - Free a messages array.
737 */
738
739void
740_cupsMessageFree(cups_array_t *a) /* I - Message array */
741{
742 _cups_message_t *m; /* Current message */
743
744
745 for (m = (_cups_message_t *)cupsArrayFirst(a);
746 m;
747 m = (_cups_message_t *)cupsArrayNext(a))
748 {
749 /*
750 * Remove the message from the array, then free the message and strings.
751 */
752
753 cupsArrayRemove(a, m);
754
755 if (m->id)
756 free(m->id);
757
758 if (m->str)
759 free(m->str);
760
761 free(m);
762 }
763
764 /*
765 * Free the array...
766 */
767
768 cupsArrayDelete(a);
769}
770
771
772/*
773 * '_cupsMessageLoad()' - Load a .po file into a messages array.
774 */
775
776cups_array_t * /* O - New message array */
777_cupsMessageLoad(const char *filename) /* I - Message catalog to load */
778{
779 cups_file_t *fp; /* Message file */
780 cups_array_t *a; /* Message array */
781 _cups_message_t *m; /* Current message */
782 char s[4096], /* String buffer */
783 *ptr, /* Pointer into buffer */
784 *temp; /* New string */
785 int length; /* Length of combined strings */
786
787
bd7854cb 788 DEBUG_printf(("_cupsMessageLoad(filename=\"%s\")\n", filename));
789
ef416fc2 790 /*
791 * Create an array to hold the messages...
792 */
793
794 if ((a = cupsArrayNew((cups_array_func_t)cups_message_compare, NULL)) == NULL)
795 return (NULL);
796
797 /*
798 * Open the message catalog file...
799 */
800
801 if ((fp = cupsFileOpen(filename, "r")) == NULL)
802 return (a);
803
804 /*
805 * Read messages from the catalog file until EOF...
806 *
807 * The format is the GNU gettext .po format, which is fairly simple:
808 *
809 * msgid "some text"
810 * msgstr "localized text"
811 *
bd7854cb 812 * The ID and localized text can span multiple lines using the form:
ef416fc2 813 *
bd7854cb 814 * msgid ""
815 * "some long text"
816 * msgstr ""
817 * "localized text spanning "
ef416fc2 818 * "multiple lines"
819 */
820
821 m = NULL;
822
823 while (cupsFileGets(fp, s, sizeof(s)) != NULL)
824 {
825 /*
826 * Skip blank and comment lines...
827 */
828
829 if (s[0] == '#' || !s[0])
830 continue;
831
832 /*
833 * Strip the trailing quote...
834 */
835
836 if ((ptr = strrchr(s, '\"')) == NULL)
837 continue;
838
839 *ptr = '\0';
840
841 /*
842 * Find start of value...
843 */
844
845 if ((ptr = strchr(s, '\"')) == NULL)
846 continue;
847
848 ptr ++;
849
850 /*
851 * Unquote the text...
852 */
853
854 cups_unquote(ptr, ptr);
855
856 /*
857 * Create or add to a message...
858 */
859
860 if (!strncmp(s, "msgid", 5))
861 {
bd7854cb 862 /*
863 * Add previous message as needed...
864 */
865
866 if (m)
867 cupsArrayAdd(a, m);
868
869 /*
870 * Create a new message with the given msgid string...
871 */
872
ef416fc2 873 if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
874 {
875 cupsFileClose(fp);
876 return (a);
877 }
878
879 m->id = strdup(ptr);
ef416fc2 880 }
bd7854cb 881 else if (s[0] == '\"' && m)
ef416fc2 882 {
bd7854cb 883 /*
884 * Append to current string...
885 */
ef416fc2 886
b86bc4cf 887 length = (int)strlen(m->str ? m->str : m->id);
ef416fc2 888
bd7854cb 889 if ((temp = realloc(m->str ? m->str : m->id,
890 length + strlen(ptr) + 1)) == NULL)
891 {
892 cupsFileClose(fp);
893 return (a);
894 }
ef416fc2 895
bd7854cb 896 if (m->str)
897 {
ef416fc2 898 /*
bd7854cb 899 * Copy the new portion to the end of the msgstr string - safe
900 * to use strcpy because the buffer is allocated to the correct
901 * size...
ef416fc2 902 */
903
bd7854cb 904 m->str = temp;
905
ef416fc2 906 strcpy(m->str + length, ptr);
907 }
908 else
909 {
910 /*
bd7854cb 911 * Copy the new portion to the end of the msgid string - safe
912 * to use strcpy because the buffer is allocated to the correct
913 * size...
ef416fc2 914 */
915
bd7854cb 916 m->id = temp;
917
918 strcpy(m->id + length, ptr);
ef416fc2 919 }
920 }
bd7854cb 921 else if (!strncmp(s, "msgstr", 6) && m)
922 {
923 /*
924 * Set the string...
925 */
926
927 m->str = strdup(ptr);
928 }
ef416fc2 929 }
930
bd7854cb 931 /*
932 * Add the last message string to the array as needed...
933 */
934
935 if (m)
936 cupsArrayAdd(a, m);
937
ef416fc2 938 /*
939 * Close the message catalog file and return the new array...
940 */
941
942 cupsFileClose(fp);
943
944 return (a);
945}
946
947
948/*
949 * '_cupsMessageLookup()' - Lookup a message string.
950 */
951
952const char * /* O - Localized message */
953_cupsMessageLookup(cups_array_t *a, /* I - Message array */
954 const char *m) /* I - Message */
955{
956 _cups_message_t key, /* Search key */
957 *match; /* Matching message */
958
959
960 /*
961 * Lookup the message string; if it doesn't exist in the catalog,
962 * then return the message that was passed to us...
963 */
964
965 key.id = (char *)m;
966 match = (_cups_message_t *)cupsArrayFind(a, &key);
967
968 if (match && match->str)
969 return (match->str);
970 else
971 return (m);
972}
973
974
ef416fc2 975#ifdef __APPLE__
976/*
977 * Code & data to translate OSX's language names to their ISO 639-1 locale.
978 *
979 * The first version uses the new CoreFoundation API added in 10.3 (Panther),
980 * the second is for 10.2 (Jaguar).
981 */
982
983# ifdef HAVE_CF_LOCALE_ID
b94498cf 984
985typedef struct
986{
987 const char * const name; /* Language name */
988 const char * const locale; /* Locale name */
989} _apple_name_locale_t;
990
991static const _apple_name_locale_t apple_name_locale[] =
992{
993 { "en" , "en_US" },
db1f069b 994 { "nb" , "no" },
b94498cf 995 { "zh-Hans" , "zh_CN" },
996 { "zh-Hant" , "zh_TW" }
997};
998
db1f069b 999
ef416fc2 1000/*
1001 * 'appleLangDefault()' - Get the default locale string.
1002 */
1003
1004static const char * /* O - Locale string */
1005appleLangDefault(void)
1006{
b94498cf 1007 int i; /* Looping var */
db1f069b
MS
1008 CFBundleRef bundle; /* Main bundle (if any) */
1009 CFArrayRef bundleList; /* List of localizations in bundle */
ef416fc2 1010 CFPropertyListRef localizationList;
1011 /* List of localization data */
1012 CFStringRef languageName; /* Current name */
1013 CFStringRef localeName; /* Canonical from of name */
26d47ec6 1014 char *lang; /* LANG environment variable */
ef416fc2 1015 _cups_globals_t *cg = _cupsGlobals();
1016 /* Pointer to library globals */
1017
1018
1019 /*
1020 * Only do the lookup and translation the first time.
1021 */
1022
1023 if (!cg->language[0])
1024 {
26d47ec6 1025 if ((lang = getenv("LANG")))
db1f069b 1026 {
26d47ec6 1027 strlcpy(cg->language, lang, sizeof(cg->language));
db1f069b
MS
1028 return (cg->language);
1029 }
1030 else if ((bundle = CFBundleGetMainBundle()) != NULL &&
1031 (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
ef416fc2 1032 {
26d47ec6 1033 localizationList =
db1f069b 1034 CFBundleCopyPreferredLocalizationsFromArray(bundleList);
26d47ec6 1035
db1f069b
MS
1036 CFRelease(bundleList);
1037 }
1038 else
1039 localizationList =
1040 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1041 kCFPreferencesCurrentApplication);
1042
1043 if (localizationList)
1044 {
1045 if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
1046 CFArrayGetCount(localizationList) > 0)
ef416fc2 1047 {
db1f069b
MS
1048 languageName = CFArrayGetValueAtIndex(localizationList, 0);
1049
1050 if (languageName &&
1051 CFGetTypeID(languageName) == CFStringGetTypeID())
26d47ec6 1052 {
db1f069b
MS
1053 localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(
1054 kCFAllocatorDefault, languageName);
ef416fc2 1055
db1f069b
MS
1056 if (localeName)
1057 {
1058 CFStringGetCString(localeName, cg->language, sizeof(cg->language),
1059 kCFStringEncodingASCII);
1060 CFRelease(localeName);
ef416fc2 1061
db1f069b
MS
1062 DEBUG_printf(("appleLangDefault: cg->language=\"%s\"\n",
1063 cg->language));
26d47ec6 1064
db1f069b
MS
1065 /*
1066 * Map new language identifiers to locales...
1067 */
b94498cf 1068
db1f069b
MS
1069 for (i = 0;
1070 i < sizeof(apple_name_locale) / sizeof(apple_name_locale[0]);
1071 i++)
1072 {
1073 if (!strcmp(cg->language, apple_name_locale[i].name))
b94498cf 1074 {
db1f069b
MS
1075 DEBUG_printf(("appleLangDefault: mapping \"%s\" to \"%s\"...\n",
1076 cg->language, apple_name_locale[i].locale));
1077 strlcpy(cg->language, apple_name_locale[i].locale,
1078 sizeof(cg->language));
1079 break;
b94498cf 1080 }
db1f069b 1081 }
b94498cf 1082
db1f069b
MS
1083 /*
1084 * Convert language subtag into region subtag...
1085 */
b94498cf 1086
db1f069b
MS
1087 if (cg->language[2] == '-')
1088 cg->language[2] = '_';
b94498cf 1089
db1f069b
MS
1090 if (!strchr(cg->language, '.'))
1091 strlcat(cg->language, ".UTF-8", sizeof(cg->language));
1092 }
26d47ec6 1093 }
26d47ec6 1094 }
db1f069b
MS
1095
1096 CFRelease(localizationList);
ef416fc2 1097 }
1098
1099 /*
1100 * If we didn't find the language, default to en_US...
1101 */
1102
1103 if (!cg->language[0])
1104 strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
1105 }
1106
1107 /*
1108 * Return the cached locale...
1109 */
1110
1111 return (cg->language);
1112}
1113# else
1114/*
1115 * Code & data to translate OSX 10.2's language names to their ISO 639-1
1116 * locale.
1117 */
1118
1119typedef struct
1120{
1121 const char * const name; /* Language name */
1122 const char * const locale; /* Locale name */
1123} _apple_name_locale_t;
1124
1125static const _apple_name_locale_t apple_name_locale[] =
1126{
1127 { "English" , "en_US.UTF-8" }, { "French" , "fr.UTF-8" },
1128 { "German" , "de.UTF-8" }, { "Italian" , "it.UTF-8" },
1129 { "Dutch" , "nl.UTF-8" }, { "Swedish" , "sv.UTF-8" },
1130 { "Spanish" , "es.UTF-8" }, { "Danish" , "da.UTF-8" },
1131 { "Portuguese" , "pt.UTF-8" }, { "Norwegian" , "no.UTF-8" },
1132 { "Hebrew" , "he.UTF-8" }, { "Japanese" , "ja.UTF-8" },
1133 { "Arabic" , "ar.UTF-8" }, { "Finnish" , "fi.UTF-8" },
1134 { "Greek" , "el.UTF-8" }, { "Icelandic" , "is.UTF-8" },
1135 { "Maltese" , "mt.UTF-8" }, { "Turkish" , "tr.UTF-8" },
1136 { "Croatian" , "hr.UTF-8" }, { "Chinese" , "zh.UTF-8" },
1137 { "Urdu" , "ur.UTF-8" }, { "Hindi" , "hi.UTF-8" },
1138 { "Thai" , "th.UTF-8" }, { "Korean" , "ko.UTF-8" },
1139 { "Lithuanian" , "lt.UTF-8" }, { "Polish" , "pl.UTF-8" },
1140 { "Hungarian" , "hu.UTF-8" }, { "Estonian" , "et.UTF-8" },
1141 { "Latvian" , "lv.UTF-8" }, { "Sami" , "se.UTF-8" },
1142 { "Faroese" , "fo.UTF-8" }, { "Farsi" , "fa.UTF-8" },
1143 { "Russian" , "ru.UTF-8" }, { "Chinese" , "zh.UTF-8" },
1144 { "Dutch" , "nl.UTF-8" }, { "Irish" , "ga.UTF-8" },
1145 { "Albanian" , "sq.UTF-8" }, { "Romanian" , "ro.UTF-8" },
1146 { "Czech" , "cs.UTF-8" }, { "Slovak" , "sk.UTF-8" },
1147 { "Slovenian" , "sl.UTF-8" }, { "Yiddish" , "yi.UTF-8" },
1148 { "Serbian" , "sr.UTF-8" }, { "Macedonian" , "mk.UTF-8" },
1149 { "Bulgarian" , "bg.UTF-8" }, { "Ukrainian" , "uk.UTF-8" },
1150 { "Byelorussian", "be.UTF-8" }, { "Uzbek" , "uz.UTF-8" },
1151 { "Kazakh" , "kk.UTF-8" }, { "Azerbaijani", "az.UTF-8" },
1152 { "Azerbaijani" , "az.UTF-8" }, { "Armenian" , "hy.UTF-8" },
1153 { "Georgian" , "ka.UTF-8" }, { "Moldavian" , "mo.UTF-8" },
1154 { "Kirghiz" , "ky.UTF-8" }, { "Tajiki" , "tg.UTF-8" },
1155 { "Turkmen" , "tk.UTF-8" }, { "Mongolian" , "mn.UTF-8" },
1156 { "Mongolian" , "mn.UTF-8" }, { "Pashto" , "ps.UTF-8" },
1157 { "Kurdish" , "ku.UTF-8" }, { "Kashmiri" , "ks.UTF-8" },
1158 { "Sindhi" , "sd.UTF-8" }, { "Tibetan" , "bo.UTF-8" },
1159 { "Nepali" , "ne.UTF-8" }, { "Sanskrit" , "sa.UTF-8" },
1160 { "Marathi" , "mr.UTF-8" }, { "Bengali" , "bn.UTF-8" },
1161 { "Assamese" , "as.UTF-8" }, { "Gujarati" , "gu.UTF-8" },
1162 { "Punjabi" , "pa.UTF-8" }, { "Oriya" , "or.UTF-8" },
1163 { "Malayalam" , "ml.UTF-8" }, { "Kannada" , "kn.UTF-8" },
1164 { "Tamil" , "ta.UTF-8" }, { "Telugu" , "te.UTF-8" },
1165 { "Sinhalese" , "si.UTF-8" }, { "Burmese" , "my.UTF-8" },
1166 { "Khmer" , "km.UTF-8" }, { "Lao" , "lo.UTF-8" },
1167 { "Vietnamese" , "vi.UTF-8" }, { "Indonesian" , "id.UTF-8" },
1168 { "Tagalog" , "tl.UTF-8" }, { "Malay" , "ms.UTF-8" },
1169 { "Malay" , "ms.UTF-8" }, { "Amharic" , "am.UTF-8" },
1170 { "Tigrinya" , "ti.UTF-8" }, { "Oromo" , "om.UTF-8" },
1171 { "Somali" , "so.UTF-8" }, { "Swahili" , "sw.UTF-8" },
1172 { "Kinyarwanda" , "rw.UTF-8" }, { "Rundi" , "rn.UTF-8" },
1173 { "Nyanja" , "" }, { "Malagasy" , "mg.UTF-8" },
1174 { "Esperanto" , "eo.UTF-8" }, { "Welsh" , "cy.UTF-8" },
1175 { "Basque" , "eu.UTF-8" }, { "Catalan" , "ca.UTF-8" },
1176 { "Latin" , "la.UTF-8" }, { "Quechua" , "qu.UTF-8" },
1177 { "Guarani" , "gn.UTF-8" }, { "Aymara" , "ay.UTF-8" },
1178 { "Tatar" , "tt.UTF-8" }, { "Uighur" , "ug.UTF-8" },
1179 { "Dzongkha" , "dz.UTF-8" }, { "Javanese" , "jv.UTF-8" },
1180 { "Sundanese" , "su.UTF-8" }, { "Galician" , "gl.UTF-8" },
1181 { "Afrikaans" , "af.UTF-8" }, { "Breton" , "br.UTF-8" },
1182 { "Inuktitut" , "iu.UTF-8" }, { "Scottish" , "gd.UTF-8" },
1183 { "Manx" , "gv.UTF-8" }, { "Irish" , "ga.UTF-8" },
1184 { "Tongan" , "to.UTF-8" }, { "Greek" , "el.UTF-8" },
1185 { "Greenlandic" , "kl.UTF-8" }, { "Azerbaijani", "az.UTF-8" }
1186};
1187
1188
1189/*
1190 * 'appleLangDefault()' - Get the default locale string.
1191 */
1192
1193static const char * /* O - Locale string */
1194appleLangDefault(void)
1195{
1196 int i; /* Looping var */
1197 CFPropertyListRef localizationList;
1198 /* List of localization data */
1199 CFStringRef localizationName;
1200 /* Current name */
1201 char buff[256]; /* Temporary buffer */
1202 _cups_globals_t *cg = _cupsGlobals();
1203 /* Pointer to library globals */
26d47ec6 1204 char *lang; /* LANG environment variable */
ef416fc2 1205
1206
1207 /*
1208 * Only do the lookup and translation the first time.
1209 */
1210
26d47ec6 1211 if (!cg->language[0])
ef416fc2 1212 {
26d47ec6 1213 if ((lang = getenv("LANG")))
1214 strlcpy(cg->language, lang, sizeof(cg->language));
1215 else
ef416fc2 1216 {
26d47ec6 1217 localizationList =
1218 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1219 kCFPreferencesCurrentApplication);
ef416fc2 1220
26d47ec6 1221 if (localizationList != NULL)
1222 {
1223 if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
1224 CFArrayGetCount(localizationList) > 0)
ef416fc2 1225 {
26d47ec6 1226 localizationName = CFArrayGetValueAtIndex(localizationList, 0);
ef416fc2 1227
26d47ec6 1228 if (localizationName != NULL &&
1229 CFGetTypeID(localizationName) == CFStringGetTypeID())
ef416fc2 1230 {
26d47ec6 1231 CFIndex length = CFStringGetLength(localizationName);
ef416fc2 1232
26d47ec6 1233 if (length <= sizeof(buff) &&
1234 CFStringGetCString(localizationName, buff, sizeof(buff),
1235 kCFStringEncodingASCII))
ef416fc2 1236 {
26d47ec6 1237 buff[sizeof(buff) - 1] = '\0';
1238
1239 for (i = 0;
1240 i < sizeof(apple_name_locale) / sizeof(apple_name_locale[0]);
1241 i++)
ef416fc2 1242 {
26d47ec6 1243 if (!strcasecmp(buff, apple_name_locale[i].name))
1244 {
1245 strlcpy(cg->language, apple_name_locale[i].locale,
1246 sizeof(cg->language));
1247 break;
1248 }
ef416fc2 1249 }
1250 }
1251 }
1252 }
ef416fc2 1253
26d47ec6 1254 CFRelease(localizationList);
1255 }
ef416fc2 1256 }
1257
1258 /*
1259 * If we didn't find the language, default to en_US...
1260 */
1261
26d47ec6 1262 if (!cg->language[0])
1263 strlcpy(cg->language, apple_name_locale[0].locale, sizeof(cg->language));
ef416fc2 1264 }
1265
1266 /*
1267 * Return the cached locale...
1268 */
1269
1270 return (cg->language);
1271}
1272# endif /* HAVE_CF_LOCALE_ID */
1273#endif /* __APPLE__ */
1274
1275
1276/*
1277 * 'cups_cache_lookup()' - Lookup a language in the cache...
1278 */
1279
1280static cups_lang_t * /* O - Language data or NULL */
1281cups_cache_lookup(const char *name,/* I - Name of locale */
1282 cups_encoding_t encoding)
1283 /* I - Encoding of locale */
1284{
1285 cups_lang_t *lang; /* Current language */
1286
1287
1288 DEBUG_printf(("cups_cache_lookup(name=\"%s\", encoding=%d(%s))\n", name,
1289 encoding, encoding == CUPS_AUTO_ENCODING ? "auto" :
1290 lang_encodings[encoding]));
1291
1292 /*
1293 * Loop through the cache and return a match if found...
1294 */
1295
d6ae789d 1296 for (lang = lang_cache; lang != NULL; lang = lang->next)
ef416fc2 1297 {
1298 DEBUG_printf(("cups_cache_lookup: lang=%p, language=\"%s\", encoding=%d(%s)\n",
1299 lang, lang->language, lang->encoding,
1300 lang_encodings[lang->encoding]));
1301
1302 if (!strcmp(lang->language, name) &&
1303 (encoding == CUPS_AUTO_ENCODING || encoding == lang->encoding))
1304 {
1305 lang->used ++;
1306
1307 DEBUG_puts("cups_cache_lookup: returning match!");
1308
1309 return (lang);
1310 }
1311 }
1312
1313 DEBUG_puts("cups_cache_lookup: returning NULL!");
1314
1315 return (NULL);
1316}
1317
1318
1319/*
1320 * 'cups_message_compare()' - Compare two messages.
1321 */
1322
1323static int /* O - Result of comparison */
1324cups_message_compare(
1325 _cups_message_t *m1, /* I - First message */
1326 _cups_message_t *m2) /* I - Second message */
1327{
1328 return (strcmp(m1->id, m2->id));
1329}
1330
1331
1332/*
1333 * 'cups_unquote()' - Unquote characters in strings...
1334 */
1335
1336static void
1337cups_unquote(char *d, /* O - Unquoted string */
1338 const char *s) /* I - Original string */
1339{
1340 while (*s)
1341 {
1342 if (*s == '\\')
1343 {
1344 s ++;
1345 if (isdigit(*s))
1346 {
1347 *d = 0;
1348
1349 while (isdigit(*s))
1350 {
1351 *d = *d * 8 + *s - '0';
1352 s ++;
1353 }
d09495fa 1354
1355 d ++;
ef416fc2 1356 }
1357 else
1358 {
1359 if (*s == 'n')
1360 *d ++ = '\n';
1361 else if (*s == 'r')
1362 *d ++ = '\r';
1363 else if (*s == 't')
1364 *d ++ = '\t';
1365 else
1366 *d++ = *s;
1367
1368 s ++;
1369 }
1370 }
1371 else
1372 *d++ = *s++;
1373 }
1374
1375 *d = '\0';
1376}
1377
1378
1379/*
2e4ff8af 1380 * End of "$Id: language.c 6916 2007-09-05 21:14:08Z mike $".
ef416fc2 1381 */