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