]> git.ipfire.org Git - thirdparty/cups.git/blob - cups/language.c
Update Apple language ID mapping to POSIX locale IDs (rdar://32419311)
[thirdparty/cups.git] / cups / language.c
1 /*
2 * I18N/language support for CUPS.
3 *
4 * Copyright 2007-2017 by Apple Inc.
5 * Copyright 1997-2007 by Easy Software Products.
6 *
7 * These coded instructions, statements, and computer programs are the
8 * property of Apple Inc. and are protected by Federal copyright
9 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
10 * which should have been included with this file. If this file is
11 * missing or damaged, see the license at "http://www.cups.org/".
12 *
13 * This file is subject to the Apple OS-Developed Software exception.
14 */
15
16 /*
17 * Include necessary headers...
18 */
19
20 #include "cups-private.h"
21 #ifdef HAVE_LANGINFO_H
22 # include <langinfo.h>
23 #endif /* HAVE_LANGINFO_H */
24 #ifdef WIN32
25 # include <io.h>
26 #else
27 # include <unistd.h>
28 #endif /* WIN32 */
29 #ifdef HAVE_COREFOUNDATION_H
30 # include <CoreFoundation/CoreFoundation.h>
31 #endif /* HAVE_COREFOUNDATION_H */
32
33
34 /*
35 * Local globals...
36 */
37
38 static _cups_mutex_t lang_mutex = _CUPS_MUTEX_INITIALIZER;
39 /* Mutex to control access to cache */
40 static cups_lang_t *lang_cache = NULL;
41 /* Language string cache */
42 static const char * const lang_encodings[] =
43 { /* Encoding strings */
44 "us-ascii", "iso-8859-1",
45 "iso-8859-2", "iso-8859-3",
46 "iso-8859-4", "iso-8859-5",
47 "iso-8859-6", "iso-8859-7",
48 "iso-8859-8", "iso-8859-9",
49 "iso-8859-10", "utf-8",
50 "iso-8859-13", "iso-8859-14",
51 "iso-8859-15", "cp874",
52 "cp1250", "cp1251",
53 "cp1252", "cp1253",
54 "cp1254", "cp1255",
55 "cp1256", "cp1257",
56 "cp1258", "koi8-r",
57 "koi8-u", "iso-8859-11",
58 "iso-8859-16", "mac",
59 "unknown", "unknown",
60 "unknown", "unknown",
61 "unknown", "unknown",
62 "unknown", "unknown",
63 "unknown", "unknown",
64 "unknown", "unknown",
65 "unknown", "unknown",
66 "unknown", "unknown",
67 "unknown", "unknown",
68 "unknown", "unknown",
69 "unknown", "unknown",
70 "unknown", "unknown",
71 "unknown", "unknown",
72 "unknown", "unknown",
73 "unknown", "unknown",
74 "unknown", "unknown",
75 "unknown", "unknown",
76 "cp932", "cp936",
77 "cp949", "cp950",
78 "cp1361", "unknown",
79 "unknown", "unknown",
80 "unknown", "unknown",
81 "unknown", "unknown",
82 "unknown", "unknown",
83 "unknown", "unknown",
84 "unknown", "unknown",
85 "unknown", "unknown",
86 "unknown", "unknown",
87 "unknown", "unknown",
88 "unknown", "unknown",
89 "unknown", "unknown",
90 "unknown", "unknown",
91 "unknown", "unknown",
92 "unknown", "unknown",
93 "unknown", "unknown",
94 "unknown", "unknown",
95 "unknown", "unknown",
96 "unknown", "unknown",
97 "unknown", "unknown",
98 "unknown", "unknown",
99 "unknown", "unknown",
100 "unknown", "unknown",
101 "unknown", "unknown",
102 "unknown", "unknown",
103 "unknown", "unknown",
104 "unknown", "unknown",
105 "unknown", "unknown",
106 "unknown", "unknown",
107 "unknown", "unknown",
108 "euc-cn", "euc-jp",
109 "euc-kr", "euc-tw",
110 "shift_jisx0213"
111 };
112
113 #ifdef __APPLE__
114 typedef struct
115 {
116 const char * const language; /* Language ID */
117 const char * const locale; /* Locale ID */
118 } _apple_language_locale_t;
119
120 static const _apple_language_locale_t apple_language_locale[] =
121 { /* Language to locale ID LUT */
122 { "en", "en_US" },
123 { "nb", "no" },
124 { "nb_NO", "no" },
125 { "zh-Hans", "zh_CN" },
126 { "zh_HANS", "zh_CN" },
127 { "zh-Hant", "zh_TW" },
128 { "zh_HANT", "zh_TW" },
129 { "zh-Hant_CN", "zh_TW" }
130 };
131 #endif /* __APPLE__ */
132
133
134 /*
135 * Local functions...
136 */
137
138
139 #ifdef __APPLE__
140 static const char *appleLangDefault(void);
141 # ifdef CUPS_BUNDLEDIR
142 # ifndef CF_RETURNS_RETAINED
143 # if __has_feature(attribute_cf_returns_retained)
144 # define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
145 # else
146 # define CF_RETURNS_RETAINED
147 # endif /* __has_feature(attribute_cf_returns_retained) */
148 # endif /* !CF_RETURNED_RETAINED */
149 static cups_array_t *appleMessageLoad(const char *locale)
150 CF_RETURNS_RETAINED;
151 # endif /* CUPS_BUNDLEDIR */
152 #endif /* __APPLE__ */
153 static cups_lang_t *cups_cache_lookup(const char *name,
154 cups_encoding_t encoding);
155 static int cups_message_compare(_cups_message_t *m1,
156 _cups_message_t *m2);
157 static void cups_message_free(_cups_message_t *m);
158 static void cups_message_load(cups_lang_t *lang);
159 static void cups_unquote(char *d, const char *s);
160
161
162 #ifdef __APPLE__
163 /*
164 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
165 * locale ID.
166 */
167
168 const char * /* O - Language ID */
169 _cupsAppleLanguage(const char *locale, /* I - Locale ID */
170 char *language,/* I - Language ID buffer */
171 size_t langsize) /* I - Size of language ID buffer */
172 {
173 int i; /* Looping var */
174 CFStringRef localeid, /* CF locale identifier */
175 langid; /* CF language identifier */
176
177
178 /*
179 * Copy the locale name and convert, as needed, to the Apple-specific
180 * locale identifier...
181 */
182
183 switch (strlen(locale))
184 {
185 default :
186 /*
187 * Invalid locale...
188 */
189
190 strlcpy(language, "en", langsize);
191 break;
192
193 case 2 :
194 strlcpy(language, locale, langsize);
195 break;
196
197 case 5 :
198 strlcpy(language, locale, langsize);
199
200 if (language[2] == '-')
201 {
202 /*
203 * Convert ll-cc to ll_CC...
204 */
205
206 language[2] = '_';
207 language[3] = (char)toupper(language[3] & 255);
208 language[4] = (char)toupper(language[4] & 255);
209 }
210 break;
211 }
212
213 for (i = 0;
214 i < (int)(sizeof(apple_language_locale) /
215 sizeof(apple_language_locale[0]));
216 i ++)
217 if (!strcmp(locale, apple_language_locale[i].locale))
218 {
219 strlcpy(language, apple_language_locale[i].language, sizeof(language));
220 break;
221 }
222
223 /*
224 * Attempt to map the locale ID to a language ID...
225 */
226
227 if ((localeid = CFStringCreateWithCString(kCFAllocatorDefault, language,
228 kCFStringEncodingASCII)) != NULL)
229 {
230 if ((langid = CFLocaleCreateCanonicalLanguageIdentifierFromString(
231 kCFAllocatorDefault, localeid)) != NULL)
232 {
233 CFStringGetCString(langid, language, (CFIndex)langsize, kCFStringEncodingASCII);
234 CFRelease(langid);
235 }
236
237 CFRelease(localeid);
238 }
239
240 /*
241 * Return what we got...
242 */
243
244 return (language);
245 }
246
247
248 /*
249 * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID.
250 */
251
252 const char * /* O - Locale */
253 _cupsAppleLocale(CFStringRef languageName, /* I - Apple language ID */
254 char *locale, /* I - Buffer for locale */
255 size_t localesize) /* I - Size of buffer */
256 {
257 int i; /* Looping var */
258 CFStringRef localeName; /* Locale as a CF string */
259
260
261 localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, languageName);
262
263 if (localeName)
264 {
265 /*
266 * Copy the locale name and tweak as needed...
267 */
268
269 if (!CFStringGetCString(localeName, locale, (CFIndex)localesize, kCFStringEncodingASCII))
270 *locale = '\0';
271
272 CFRelease(localeName);
273
274 /*
275 * Map new language identifiers to locales...
276 */
277
278 for (i = 0;
279 i < (int)(sizeof(apple_language_locale) /
280 sizeof(apple_language_locale[0]));
281 i ++)
282 {
283 size_t len = strlen(apple_language_locale[i].language);
284
285 if (!strcmp(locale, apple_language_locale[i].language) ||
286 (!strncmp(locale, apple_language_locale[i].language, len) && (locale[len] == '_' || locale[len] == '-')))
287 {
288 strlcpy(locale, apple_language_locale[i].locale, localesize);
289 break;
290 }
291 }
292 }
293 else
294 {
295 /*
296 * Just try the Apple language name...
297 */
298
299 if (!CFStringGetCString(languageName, locale, (CFIndex)localesize, kCFStringEncodingASCII))
300 *locale = '\0';
301 }
302
303 if (!*locale)
304 return (NULL);
305
306 /*
307 * Convert language subtag into region subtag...
308 */
309
310 if (locale[2] == '-')
311 locale[2] = '_';
312
313 if (!strchr(locale, '.'))
314 strlcat(locale, ".UTF-8", localesize);
315
316 return (locale);
317 }
318 #endif /* __APPLE__ */
319
320
321 /*
322 * '_cupsEncodingName()' - Return the character encoding name string
323 * for the given encoding enumeration.
324 */
325
326 const char * /* O - Character encoding */
327 _cupsEncodingName(
328 cups_encoding_t encoding) /* I - Encoding value */
329 {
330 if (encoding < CUPS_US_ASCII ||
331 encoding >= (cups_encoding_t)(sizeof(lang_encodings) / sizeof(lang_encodings[0])))
332 {
333 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
334 encoding, lang_encodings[0]));
335 return (lang_encodings[0]);
336 }
337 else
338 {
339 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
340 encoding, lang_encodings[encoding]));
341 return (lang_encodings[encoding]);
342 }
343 }
344
345
346 /*
347 * 'cupsLangDefault()' - Return the default language.
348 */
349
350 cups_lang_t * /* O - Language data */
351 cupsLangDefault(void)
352 {
353 return (cupsLangGet(NULL));
354 }
355
356
357 /*
358 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
359 * for the given language.
360 */
361
362 const char * /* O - Character encoding */
363 cupsLangEncoding(cups_lang_t *lang) /* I - Language data */
364 {
365 if (lang == NULL)
366 return ((char*)lang_encodings[0]);
367 else
368 return ((char*)lang_encodings[lang->encoding]);
369 }
370
371
372 /*
373 * 'cupsLangFlush()' - Flush all language data out of the cache.
374 */
375
376 void
377 cupsLangFlush(void)
378 {
379 cups_lang_t *lang, /* Current language */
380 *next; /* Next language */
381
382
383 /*
384 * Free all languages in the cache...
385 */
386
387 _cupsMutexLock(&lang_mutex);
388
389 for (lang = lang_cache; lang != NULL; lang = next)
390 {
391 /*
392 * Free all messages...
393 */
394
395 _cupsMessageFree(lang->strings);
396
397 /*
398 * Then free the language structure itself...
399 */
400
401 next = lang->next;
402 free(lang);
403 }
404
405 lang_cache = NULL;
406
407 _cupsMutexUnlock(&lang_mutex);
408 }
409
410
411 /*
412 * 'cupsLangFree()' - Free language data.
413 *
414 * This does not actually free anything; use @link cupsLangFlush@ for that.
415 */
416
417 void
418 cupsLangFree(cups_lang_t *lang) /* I - Language to free */
419 {
420 _cupsMutexLock(&lang_mutex);
421
422 if (lang != NULL && lang->used > 0)
423 lang->used --;
424
425 _cupsMutexUnlock(&lang_mutex);
426 }
427
428
429 /*
430 * 'cupsLangGet()' - Get a language.
431 */
432
433 cups_lang_t * /* O - Language data */
434 cupsLangGet(const char *language) /* I - Language or locale */
435 {
436 int i; /* Looping var */
437 #ifndef __APPLE__
438 char locale[255]; /* Copy of locale name */
439 #endif /* !__APPLE__ */
440 char langname[16], /* Requested language name */
441 country[16], /* Country code */
442 charset[16], /* Character set */
443 *csptr, /* Pointer to CODESET string */
444 *ptr, /* Pointer into language/charset */
445 real[48]; /* Real language name */
446 cups_encoding_t encoding; /* Encoding to use */
447 cups_lang_t *lang; /* Current language... */
448 static const char * const locale_encodings[] =
449 { /* Locale charset names */
450 "ASCII", "ISO88591", "ISO88592", "ISO88593",
451 "ISO88594", "ISO88595", "ISO88596", "ISO88597",
452 "ISO88598", "ISO88599", "ISO885910", "UTF8",
453 "ISO885913", "ISO885914", "ISO885915", "CP874",
454 "CP1250", "CP1251", "CP1252", "CP1253",
455 "CP1254", "CP1255", "CP1256", "CP1257",
456 "CP1258", "KOI8R", "KOI8U", "ISO885911",
457 "ISO885916", "MACROMAN", "", "",
458
459 "", "", "", "",
460 "", "", "", "",
461 "", "", "", "",
462 "", "", "", "",
463 "", "", "", "",
464 "", "", "", "",
465 "", "", "", "",
466 "", "", "", "",
467
468 "CP932", "CP936", "CP949", "CP950",
469 "CP1361", "", "", "",
470 "", "", "", "",
471 "", "", "", "",
472 "", "", "", "",
473 "", "", "", "",
474 "", "", "", "",
475 "", "", "", "",
476
477 "", "", "", "",
478 "", "", "", "",
479 "", "", "", "",
480 "", "", "", "",
481 "", "", "", "",
482 "", "", "", "",
483 "", "", "", "",
484 "", "", "", "",
485
486 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
487 "SHIFT_JISX0213"
488 };
489
490
491 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language));
492
493 #ifdef __APPLE__
494 /*
495 * Set the character set to UTF-8...
496 */
497
498 strlcpy(charset, "UTF8", sizeof(charset));
499
500 /*
501 * Apple's setlocale doesn't give us the user's localization
502 * preference so we have to look it up this way...
503 */
504
505 if (!language)
506 {
507 if (!getenv("SOFTWARE") || (language = getenv("LANG")) == NULL)
508 language = appleLangDefault();
509
510 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language));
511 }
512
513 #else
514 /*
515 * Set the charset to "unknown"...
516 */
517
518 charset[0] = '\0';
519
520 /*
521 * Use setlocale() to determine the currently set locale, and then
522 * fallback to environment variables to avoid setting the locale,
523 * since setlocale() is not thread-safe!
524 */
525
526 if (!language)
527 {
528 /*
529 * First see if the locale has been set; if it is still "C" or
530 * "POSIX", use the environment to get the default...
531 */
532
533 # ifdef LC_MESSAGES
534 ptr = setlocale(LC_MESSAGES, NULL);
535 # else
536 ptr = setlocale(LC_ALL, NULL);
537 # endif /* LC_MESSAGES */
538
539 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr));
540
541 if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
542 {
543 /*
544 * Get the character set from the LC_CTYPE locale setting...
545 */
546
547 if ((ptr = getenv("LC_CTYPE")) == NULL)
548 if ((ptr = getenv("LC_ALL")) == NULL)
549 if ((ptr = getenv("LANG")) == NULL)
550 ptr = "en_US";
551
552 if ((csptr = strchr(ptr, '.')) != NULL)
553 {
554 /*
555 * Extract the character set from the environment...
556 */
557
558 for (ptr = charset, csptr ++; *csptr; csptr ++)
559 if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr))
560 *ptr++ = *csptr;
561
562 *ptr = '\0';
563 }
564
565 /*
566 * Get the locale for messages from the LC_MESSAGES locale setting...
567 */
568
569 if ((ptr = getenv("LC_MESSAGES")) == NULL)
570 if ((ptr = getenv("LC_ALL")) == NULL)
571 if ((ptr = getenv("LANG")) == NULL)
572 ptr = "en_US";
573 }
574
575 if (ptr)
576 {
577 strlcpy(locale, ptr, sizeof(locale));
578 language = locale;
579
580 /*
581 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
582 */
583
584 if (!strncmp(locale, "nb", 2))
585 locale[1] = 'o';
586
587 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language));
588 }
589 }
590 #endif /* __APPLE__ */
591
592 /*
593 * If "language" is NULL at this point, then chances are we are using
594 * a language that is not installed for the base OS.
595 */
596
597 if (!language)
598 {
599 /*
600 * Switch to the POSIX ("C") locale...
601 */
602
603 language = "C";
604 }
605
606 #ifdef CODESET
607 /*
608 * On systems that support the nl_langinfo(CODESET) call, use
609 * this value as the character set...
610 */
611
612 if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
613 {
614 /*
615 * Copy all of the letters and numbers in the CODESET string...
616 */
617
618 for (ptr = charset; *csptr; csptr ++)
619 if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1))
620 *ptr++ = *csptr;
621
622 *ptr = '\0';
623
624 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
625 "nl_langinfo(CODESET)...", charset));
626 }
627 #endif /* CODESET */
628
629 /*
630 * If we don't have a character set by now, default to UTF-8...
631 */
632
633 if (!charset[0])
634 strlcpy(charset, "UTF8", sizeof(charset));
635
636 /*
637 * Parse the language string passed in to a locale string. "C" is the
638 * standard POSIX locale and is copied unchanged. Otherwise the
639 * language string is converted from ll-cc[.charset] (language-country)
640 * to ll_CC[.CHARSET] to match the file naming convention used by all
641 * POSIX-compliant operating systems. Invalid language names are mapped
642 * to the POSIX locale.
643 */
644
645 country[0] = '\0';
646
647 if (language == NULL || !language[0] ||
648 !strcmp(language, "POSIX"))
649 strlcpy(langname, "C", sizeof(langname));
650 else
651 {
652 /*
653 * Copy the parts of the locale string over safely...
654 */
655
656 for (ptr = langname; *language; language ++)
657 if (*language == '_' || *language == '-' || *language == '.')
658 break;
659 else if (ptr < (langname + sizeof(langname) - 1))
660 *ptr++ = (char)tolower(*language & 255);
661
662 *ptr = '\0';
663
664 if (*language == '_' || *language == '-')
665 {
666 /*
667 * Copy the country code...
668 */
669
670 for (language ++, ptr = country; *language; language ++)
671 if (*language == '.')
672 break;
673 else if (ptr < (country + sizeof(country) - 1))
674 *ptr++ = (char)toupper(*language & 255);
675
676 *ptr = '\0';
677 }
678
679 if (*language == '.' && !charset[0])
680 {
681 /*
682 * Copy the encoding...
683 */
684
685 for (language ++, ptr = charset; *language; language ++)
686 if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1))
687 *ptr++ = (char)toupper(*language & 255);
688
689 *ptr = '\0';
690 }
691
692 /*
693 * Force a POSIX locale for an invalid language name...
694 */
695
696 if (strlen(langname) != 2)
697 {
698 strlcpy(langname, "C", sizeof(langname));
699 country[0] = '\0';
700 charset[0] = '\0';
701 }
702 }
703
704 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
705 langname, country, charset));
706
707 /*
708 * Figure out the desired encoding...
709 */
710
711 encoding = CUPS_AUTO_ENCODING;
712
713 if (charset[0])
714 {
715 for (i = 0;
716 i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
717 i ++)
718 if (!_cups_strcasecmp(charset, locale_encodings[i]))
719 {
720 encoding = (cups_encoding_t)i;
721 break;
722 }
723
724 if (encoding == CUPS_AUTO_ENCODING)
725 {
726 /*
727 * Map alternate names for various character sets...
728 */
729
730 if (!_cups_strcasecmp(charset, "iso-2022-jp") ||
731 !_cups_strcasecmp(charset, "sjis"))
732 encoding = CUPS_WINDOWS_932;
733 else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
734 encoding = CUPS_WINDOWS_936;
735 else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
736 encoding = CUPS_WINDOWS_949;
737 else if (!_cups_strcasecmp(charset, "big5"))
738 encoding = CUPS_WINDOWS_950;
739 }
740 }
741
742 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding,
743 encoding == CUPS_AUTO_ENCODING ? "auto" :
744 lang_encodings[encoding]));
745
746 /*
747 * See if we already have this language/country loaded...
748 */
749
750 if (country[0])
751 snprintf(real, sizeof(real), "%s_%s", langname, country);
752 else
753 strlcpy(real, langname, sizeof(real));
754
755 _cupsMutexLock(&lang_mutex);
756
757 if ((lang = cups_cache_lookup(real, encoding)) != NULL)
758 {
759 _cupsMutexUnlock(&lang_mutex);
760
761 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real));
762
763 return (lang);
764 }
765
766 /*
767 * See if there is a free language available; if so, use that
768 * record...
769 */
770
771 for (lang = lang_cache; lang != NULL; lang = lang->next)
772 if (lang->used == 0)
773 break;
774
775 if (lang == NULL)
776 {
777 /*
778 * Allocate memory for the language and add it to the cache.
779 */
780
781 if ((lang = calloc(sizeof(cups_lang_t), 1)) == NULL)
782 {
783 _cupsMutexUnlock(&lang_mutex);
784
785 return (NULL);
786 }
787
788 lang->next = lang_cache;
789 lang_cache = lang;
790 }
791 else
792 {
793 /*
794 * Free all old strings as needed...
795 */
796
797 _cupsMessageFree(lang->strings);
798 lang->strings = NULL;
799 }
800
801 /*
802 * Then assign the language and encoding fields...
803 */
804
805 lang->used ++;
806 strlcpy(lang->language, real, sizeof(lang->language));
807
808 if (encoding != CUPS_AUTO_ENCODING)
809 lang->encoding = encoding;
810 else
811 lang->encoding = CUPS_UTF8;
812
813 /*
814 * Return...
815 */
816
817 _cupsMutexUnlock(&lang_mutex);
818
819 return (lang);
820 }
821
822
823 /*
824 * '_cupsLangString()' - Get a message string.
825 *
826 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
827 * convert the string to the language encoding.
828 */
829
830 const char * /* O - Localized message */
831 _cupsLangString(cups_lang_t *lang, /* I - Language */
832 const char *message) /* I - Message */
833 {
834 const char *s; /* Localized message */
835
836 /*
837 * Range check input...
838 */
839
840 if (!lang || !message || !*message)
841 return (message);
842
843 _cupsMutexLock(&lang_mutex);
844
845 /*
846 * Load the message catalog if needed...
847 */
848
849 if (!lang->strings)
850 cups_message_load(lang);
851
852 s = _cupsMessageLookup(lang->strings, message);
853
854 _cupsMutexUnlock(&lang_mutex);
855
856 return (s);
857 }
858
859
860 /*
861 * '_cupsMessageFree()' - Free a messages array.
862 */
863
864 void
865 _cupsMessageFree(cups_array_t *a) /* I - Message array */
866 {
867 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
868 /*
869 * Release the cups.strings dictionary as needed...
870 */
871
872 if (cupsArrayUserData(a))
873 CFRelease((CFDictionaryRef)cupsArrayUserData(a));
874 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
875
876 /*
877 * Free the array...
878 */
879
880 cupsArrayDelete(a);
881 }
882
883
884 /*
885 * '_cupsMessageLoad()' - Load a .po file into a messages array.
886 */
887
888 cups_array_t * /* O - New message array */
889 _cupsMessageLoad(const char *filename, /* I - Message catalog to load */
890 int unquote) /* I - Unescape \foo in strings? */
891 {
892 cups_file_t *fp; /* Message file */
893 cups_array_t *a; /* Message array */
894 _cups_message_t *m; /* Current message */
895 char s[4096], /* String buffer */
896 *ptr, /* Pointer into buffer */
897 *temp; /* New string */
898 size_t length, /* Length of combined strings */
899 ptrlen; /* Length of string */
900
901
902 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename));
903
904 /*
905 * Create an array to hold the messages...
906 */
907
908 if ((a = _cupsMessageNew(NULL)) == NULL)
909 {
910 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
911 return (NULL);
912 }
913
914 /*
915 * Open the message catalog file...
916 */
917
918 if ((fp = cupsFileOpen(filename, "r")) == NULL)
919 {
920 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
921 strerror(errno)));
922 return (a);
923 }
924
925 /*
926 * Read messages from the catalog file until EOF...
927 *
928 * The format is the GNU gettext .po format, which is fairly simple:
929 *
930 * msgid "some text"
931 * msgstr "localized text"
932 *
933 * The ID and localized text can span multiple lines using the form:
934 *
935 * msgid ""
936 * "some long text"
937 * msgstr ""
938 * "localized text spanning "
939 * "multiple lines"
940 */
941
942 m = NULL;
943
944 while (cupsFileGets(fp, s, sizeof(s)) != NULL)
945 {
946 /*
947 * Skip blank and comment lines...
948 */
949
950 if (s[0] == '#' || !s[0])
951 continue;
952
953 /*
954 * Strip the trailing quote...
955 */
956
957 if ((ptr = strrchr(s, '\"')) == NULL)
958 continue;
959
960 *ptr = '\0';
961
962 /*
963 * Find start of value...
964 */
965
966 if ((ptr = strchr(s, '\"')) == NULL)
967 continue;
968
969 ptr ++;
970
971 /*
972 * Unquote the text...
973 */
974
975 if (unquote)
976 cups_unquote(ptr, ptr);
977
978 /*
979 * Create or add to a message...
980 */
981
982 if (!strncmp(s, "msgid", 5))
983 {
984 /*
985 * Add previous message as needed...
986 */
987
988 if (m)
989 {
990 if (m->str && m->str[0])
991 {
992 cupsArrayAdd(a, m);
993 }
994 else
995 {
996 /*
997 * Translation is empty, don't add it... (STR #4033)
998 */
999
1000 free(m->id);
1001 if (m->str)
1002 free(m->str);
1003 free(m);
1004 }
1005 }
1006
1007 /*
1008 * Create a new message with the given msgid string...
1009 */
1010
1011 if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
1012 {
1013 cupsFileClose(fp);
1014 return (a);
1015 }
1016
1017 if ((m->id = strdup(ptr)) == NULL)
1018 {
1019 free(m);
1020 cupsFileClose(fp);
1021 return (a);
1022 }
1023 }
1024 else if (s[0] == '\"' && m)
1025 {
1026 /*
1027 * Append to current string...
1028 */
1029
1030 length = strlen(m->str ? m->str : m->id);
1031 ptrlen = strlen(ptr);
1032
1033 if ((temp = realloc(m->str ? m->str : m->id, length + ptrlen + 1)) == NULL)
1034 {
1035 if (m->str)
1036 free(m->str);
1037 free(m->id);
1038 free(m);
1039
1040 cupsFileClose(fp);
1041 return (a);
1042 }
1043
1044 if (m->str)
1045 {
1046 /*
1047 * Copy the new portion to the end of the msgstr string - safe
1048 * to use memcpy because the buffer is allocated to the correct
1049 * size...
1050 */
1051
1052 m->str = temp;
1053
1054 memcpy(m->str + length, ptr, ptrlen + 1);
1055 }
1056 else
1057 {
1058 /*
1059 * Copy the new portion to the end of the msgid string - safe
1060 * to use memcpy because the buffer is allocated to the correct
1061 * size...
1062 */
1063
1064 m->id = temp;
1065
1066 memcpy(m->id + length, ptr, ptrlen + 1);
1067 }
1068 }
1069 else if (!strncmp(s, "msgstr", 6) && m)
1070 {
1071 /*
1072 * Set the string...
1073 */
1074
1075 if ((m->str = strdup(ptr)) == NULL)
1076 {
1077 free(m->id);
1078 free(m);
1079
1080 cupsFileClose(fp);
1081 return (a);
1082 }
1083 }
1084 }
1085
1086 /*
1087 * Add the last message string to the array as needed...
1088 */
1089
1090 if (m)
1091 {
1092 if (m->str && m->str[0])
1093 {
1094 cupsArrayAdd(a, m);
1095 }
1096 else
1097 {
1098 /*
1099 * Translation is empty, don't add it... (STR #4033)
1100 */
1101
1102 free(m->id);
1103 if (m->str)
1104 free(m->str);
1105 free(m);
1106 }
1107 }
1108
1109 /*
1110 * Close the message catalog file and return the new array...
1111 */
1112
1113 cupsFileClose(fp);
1114
1115 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
1116 cupsArrayCount(a)));
1117
1118 return (a);
1119 }
1120
1121
1122 /*
1123 * '_cupsMessageLookup()' - Lookup a message string.
1124 */
1125
1126 const char * /* O - Localized message */
1127 _cupsMessageLookup(cups_array_t *a, /* I - Message array */
1128 const char *m) /* I - Message */
1129 {
1130 _cups_message_t key, /* Search key */
1131 *match; /* Matching message */
1132
1133
1134 /*
1135 * Lookup the message string; if it doesn't exist in the catalog,
1136 * then return the message that was passed to us...
1137 */
1138
1139 key.id = (char *)m;
1140 match = (_cups_message_t *)cupsArrayFind(a, &key);
1141
1142 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1143 if (!match && cupsArrayUserData(a))
1144 {
1145 /*
1146 * Try looking the string up in the cups.strings dictionary...
1147 */
1148
1149 CFDictionaryRef dict; /* cups.strings dictionary */
1150 CFStringRef cfm, /* Message as a CF string */
1151 cfstr; /* Localized text as a CF string */
1152
1153 dict = (CFDictionaryRef)cupsArrayUserData(a);
1154 cfm = CFStringCreateWithCString(kCFAllocatorDefault, m,
1155 kCFStringEncodingUTF8);
1156 match = calloc(1, sizeof(_cups_message_t));
1157 match->id = strdup(m);
1158 cfstr = cfm ? CFDictionaryGetValue(dict, cfm) : NULL;
1159
1160 if (cfstr)
1161 {
1162 char buffer[1024]; /* Message buffer */
1163
1164 CFStringGetCString(cfstr, buffer, sizeof(buffer), kCFStringEncodingUTF8);
1165 match->str = strdup(buffer);
1166
1167 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...",
1168 m, buffer));
1169 }
1170 else
1171 {
1172 match->str = strdup(m);
1173
1174 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m));
1175 }
1176
1177 cupsArrayAdd(a, match);
1178
1179 if (cfm)
1180 CFRelease(cfm);
1181 }
1182 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1183
1184 if (match && match->str)
1185 return (match->str);
1186 else
1187 return (m);
1188 }
1189
1190
1191 /*
1192 * '_cupsMessageNew()' - Make a new message catalog array.
1193 */
1194
1195 cups_array_t * /* O - Array */
1196 _cupsMessageNew(void *context) /* I - User data */
1197 {
1198 return (cupsArrayNew3((cups_array_func_t)cups_message_compare, context,
1199 (cups_ahash_func_t)NULL, 0,
1200 (cups_acopy_func_t)NULL,
1201 (cups_afree_func_t)cups_message_free));
1202 }
1203
1204
1205 #ifdef __APPLE__
1206 /*
1207 * 'appleLangDefault()' - Get the default locale string.
1208 */
1209
1210 static const char * /* O - Locale string */
1211 appleLangDefault(void)
1212 {
1213 CFBundleRef bundle; /* Main bundle (if any) */
1214 CFArrayRef bundleList; /* List of localizations in bundle */
1215 CFPropertyListRef localizationList = NULL;
1216 /* List of localization data */
1217 CFStringRef languageName; /* Current name */
1218 char *lang; /* LANG environment variable */
1219 _cups_globals_t *cg = _cupsGlobals();
1220 /* Pointer to library globals */
1221
1222
1223 DEBUG_puts("2appleLangDefault()");
1224
1225 /*
1226 * Only do the lookup and translation the first time.
1227 */
1228
1229 if (!cg->language[0])
1230 {
1231 if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL)
1232 {
1233 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang));
1234 strlcpy(cg->language, lang, sizeof(cg->language));
1235 return (cg->language);
1236 }
1237 else if ((bundle = CFBundleGetMainBundle()) != NULL &&
1238 (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
1239 {
1240 CFURLRef resources = CFBundleCopyResourcesDirectoryURL(bundle);
1241
1242 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
1243
1244 if (resources)
1245 {
1246 CFStringRef cfpath = CFURLCopyPath(resources);
1247 char path[1024];
1248
1249 if (cfpath)
1250 {
1251 /*
1252 * See if we have an Info.plist file in the bundle...
1253 */
1254
1255 CFStringGetCString(cfpath, path, sizeof(path), kCFStringEncodingUTF8);
1256 DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path));
1257 strlcat(path, "Contents/Info.plist", sizeof(path));
1258
1259 if (!access(path, R_OK))
1260 localizationList = CFBundleCopyPreferredLocalizationsFromArray(bundleList);
1261 else
1262 DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL...");
1263
1264 CFRelease(cfpath);
1265 }
1266
1267 CFRelease(resources);
1268 }
1269 else
1270 DEBUG_puts("3appleLangDefault: No resource URL.");
1271
1272 CFRelease(bundleList);
1273 }
1274
1275 if (!localizationList)
1276 {
1277 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
1278
1279 localizationList =
1280 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1281 kCFPreferencesCurrentApplication);
1282 }
1283
1284 if (localizationList)
1285 {
1286 #ifdef DEBUG
1287 if (CFGetTypeID(localizationList) == CFArrayGetTypeID())
1288 DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.",
1289 (int)CFArrayGetCount(localizationList)));
1290 else
1291 DEBUG_puts("3appleLangDefault: Got localizationList but not an array.");
1292 #endif /* DEBUG */
1293
1294 if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
1295 CFArrayGetCount(localizationList) > 0)
1296 {
1297 languageName = CFArrayGetValueAtIndex(localizationList, 0);
1298
1299 if (languageName &&
1300 CFGetTypeID(languageName) == CFStringGetTypeID())
1301 {
1302 if (_cupsAppleLocale(languageName, cg->language, sizeof(cg->language)))
1303 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
1304 cg->language));
1305 else
1306 DEBUG_puts("3appleLangDefault: Unable to get locale.");
1307 }
1308 }
1309
1310 CFRelease(localizationList);
1311 }
1312
1313 /*
1314 * If we didn't find the language, default to en_US...
1315 */
1316
1317 if (!cg->language[0])
1318 {
1319 DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
1320 strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
1321 }
1322 }
1323 else
1324 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg->language));
1325
1326 /*
1327 * Return the cached locale...
1328 */
1329
1330 return (cg->language);
1331 }
1332
1333
1334 # ifdef CUPS_BUNDLEDIR
1335 /*
1336 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1337 */
1338
1339 static cups_array_t * /* O - Message catalog */
1340 appleMessageLoad(const char *locale) /* I - Locale ID */
1341 {
1342 char filename[1024], /* Path to cups.strings file */
1343 applelang[256], /* Apple language ID */
1344 baselang[3]; /* Base language */
1345 CFURLRef url; /* URL to cups.strings file */
1346 CFReadStreamRef stream = NULL; /* File stream */
1347 CFPropertyListRef plist = NULL; /* Localization file */
1348 #ifdef DEBUG
1349 CFErrorRef error = NULL; /* Error when opening file */
1350 #endif /* DEBUG */
1351
1352
1353 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale));
1354
1355 /*
1356 * Load the cups.strings file...
1357 */
1358
1359 snprintf(filename, sizeof(filename),
1360 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings",
1361 _cupsAppleLanguage(locale, applelang, sizeof(applelang)));
1362
1363 if (access(filename, 0))
1364 {
1365 /*
1366 * <rdar://problem/22086642>
1367 *
1368 * Try with original locale string...
1369 */
1370
1371 snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
1372 }
1373
1374 if (access(filename, 0))
1375 {
1376 /*
1377 * <rdar://problem/25292403>
1378 *
1379 * Try with just the language code...
1380 */
1381
1382 strlcpy(baselang, locale, sizeof(baselang));
1383 snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", baselang);
1384 }
1385
1386 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename));
1387
1388 if (access(filename, 0))
1389 {
1390 /*
1391 * Try alternate lproj directory names...
1392 */
1393
1394 if (!strncmp(locale, "en", 2))
1395 locale = "English";
1396 else if (!strncmp(locale, "nb", 2))
1397 locale = "no";
1398 else if (!strncmp(locale, "nl", 2))
1399 locale = "Dutch";
1400 else if (!strncmp(locale, "fr", 2))
1401 locale = "French";
1402 else if (!strncmp(locale, "de", 2))
1403 locale = "German";
1404 else if (!strncmp(locale, "it", 2))
1405 locale = "Italian";
1406 else if (!strncmp(locale, "ja", 2))
1407 locale = "Japanese";
1408 else if (!strncmp(locale, "es", 2))
1409 locale = "Spanish";
1410 else if (!strcmp(locale, "zh_HK") || !strncmp(locale, "zh-Hant", 7))
1411 {
1412 /*
1413 * <rdar://problem/22130168>
1414 * <rdar://problem/27245567>
1415 *
1416 * Try zh_TW first, then zh... Sigh...
1417 */
1418
1419 if (!access(CUPS_BUNDLEDIR "/Resources/zh_TW.lproj/cups.strings", 0))
1420 locale = "zh_TW";
1421 else
1422 locale = "zh";
1423 }
1424 else if (strstr(locale, "_") != NULL || strstr(locale, "-") != NULL)
1425 {
1426 /*
1427 * Drop country code, just try language...
1428 */
1429
1430 strlcpy(baselang, locale, sizeof(baselang));
1431 locale = baselang;
1432 }
1433
1434 snprintf(filename, sizeof(filename),
1435 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
1436 DEBUG_printf(("1appleMessageLoad: alternate filename=\"%s\"", filename));
1437 }
1438
1439 url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
1440 (UInt8 *)filename,
1441 (CFIndex)strlen(filename), false);
1442 if (url)
1443 {
1444 stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
1445 if (stream)
1446 {
1447 /*
1448 * Read the property list containing the localization data.
1449 *
1450 * NOTE: This code currently generates a clang "potential leak"
1451 * warning, but the object is released in _cupsMessageFree().
1452 */
1453
1454 CFReadStreamOpen(stream);
1455
1456 #ifdef DEBUG
1457 plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0,
1458 kCFPropertyListImmutable, NULL,
1459 &error);
1460 if (error)
1461 {
1462 CFStringRef msg = CFErrorCopyDescription(error);
1463 /* Error message */
1464
1465 CFStringGetCString(msg, filename, sizeof(filename),
1466 kCFStringEncodingUTF8);
1467 DEBUG_printf(("1appleMessageLoad: %s", filename));
1468
1469 CFRelease(msg);
1470 CFRelease(error);
1471 }
1472
1473 #else
1474 plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0,
1475 kCFPropertyListImmutable, NULL,
1476 NULL);
1477 #endif /* DEBUG */
1478
1479 if (plist && CFGetTypeID(plist) != CFDictionaryGetTypeID())
1480 {
1481 CFRelease(plist);
1482 plist = NULL;
1483 }
1484
1485 CFRelease(stream);
1486 }
1487
1488 CFRelease(url);
1489 }
1490
1491 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url, stream,
1492 plist));
1493
1494 /*
1495 * Create and return an empty array to act as a cache for messages, passing the
1496 * plist as the user data.
1497 */
1498
1499 return (_cupsMessageNew((void *)plist));
1500 }
1501 # endif /* CUPS_BUNDLEDIR */
1502 #endif /* __APPLE__ */
1503
1504
1505 /*
1506 * 'cups_cache_lookup()' - Lookup a language in the cache...
1507 */
1508
1509 static cups_lang_t * /* O - Language data or NULL */
1510 cups_cache_lookup(
1511 const char *name, /* I - Name of locale */
1512 cups_encoding_t encoding) /* I - Encoding of locale */
1513 {
1514 cups_lang_t *lang; /* Current language */
1515
1516
1517 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name,
1518 encoding, encoding == CUPS_AUTO_ENCODING ? "auto" :
1519 lang_encodings[encoding]));
1520
1521 /*
1522 * Loop through the cache and return a match if found...
1523 */
1524
1525 for (lang = lang_cache; lang != NULL; lang = lang->next)
1526 {
1527 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1528 "encoding=%d(%s)", (void *)lang, lang->language, lang->encoding,
1529 lang_encodings[lang->encoding]));
1530
1531 if (!strcmp(lang->language, name) &&
1532 (encoding == CUPS_AUTO_ENCODING || encoding == lang->encoding))
1533 {
1534 lang->used ++;
1535
1536 DEBUG_puts("8cups_cache_lookup: returning match!");
1537
1538 return (lang);
1539 }
1540 }
1541
1542 DEBUG_puts("8cups_cache_lookup: returning NULL!");
1543
1544 return (NULL);
1545 }
1546
1547
1548 /*
1549 * 'cups_message_compare()' - Compare two messages.
1550 */
1551
1552 static int /* O - Result of comparison */
1553 cups_message_compare(
1554 _cups_message_t *m1, /* I - First message */
1555 _cups_message_t *m2) /* I - Second message */
1556 {
1557 return (strcmp(m1->id, m2->id));
1558 }
1559
1560
1561 /*
1562 * 'cups_message_free()' - Free a message.
1563 */
1564
1565 static void
1566 cups_message_free(_cups_message_t *m) /* I - Message */
1567 {
1568 if (m->id)
1569 free(m->id);
1570
1571 if (m->str)
1572 free(m->str);
1573
1574 free(m);
1575 }
1576
1577
1578 /*
1579 * 'cups_message_load()' - Load the message catalog for a language.
1580 */
1581
1582 static void
1583 cups_message_load(cups_lang_t *lang) /* I - Language */
1584 {
1585 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1586 lang->strings = appleMessageLoad(lang->language);
1587
1588 #else
1589 char filename[1024]; /* Filename for language locale file */
1590 _cups_globals_t *cg = _cupsGlobals();
1591 /* Pointer to library globals */
1592
1593
1594 snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
1595 lang->language, lang->language);
1596
1597 if (strchr(lang->language, '_') && access(filename, 0))
1598 {
1599 /*
1600 * Country localization not available, look for generic localization...
1601 */
1602
1603 snprintf(filename, sizeof(filename), "%s/%.2s/cups_%.2s.po", cg->localedir,
1604 lang->language, lang->language);
1605
1606 if (access(filename, 0))
1607 {
1608 /*
1609 * No generic localization, so use POSIX...
1610 */
1611
1612 DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename,
1613 strerror(errno)));
1614
1615 snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir);
1616 }
1617 }
1618
1619 /*
1620 * Read the strings from the file...
1621 */
1622
1623 lang->strings = _cupsMessageLoad(filename, 1);
1624 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1625 }
1626
1627
1628 /*
1629 * 'cups_unquote()' - Unquote characters in strings...
1630 */
1631
1632 static void
1633 cups_unquote(char *d, /* O - Unquoted string */
1634 const char *s) /* I - Original string */
1635 {
1636 while (*s)
1637 {
1638 if (*s == '\\')
1639 {
1640 s ++;
1641 if (isdigit(*s))
1642 {
1643 *d = 0;
1644
1645 while (isdigit(*s))
1646 {
1647 *d = *d * 8 + *s - '0';
1648 s ++;
1649 }
1650
1651 d ++;
1652 }
1653 else
1654 {
1655 if (*s == 'n')
1656 *d ++ = '\n';
1657 else if (*s == 'r')
1658 *d ++ = '\r';
1659 else if (*s == 't')
1660 *d ++ = '\t';
1661 else
1662 *d++ = *s;
1663
1664 s ++;
1665 }
1666 }
1667 else
1668 *d++ = *s++;
1669 }
1670
1671 *d = '\0';
1672 }