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