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