]> git.ipfire.org Git - thirdparty/cups.git/blame - cups/language.c
License change: Apache License, Version 2.0.
[thirdparty/cups.git] / cups / language.c
CommitLineData
ef416fc2 1/*
7e86f2f6 2 * I18N/language support for CUPS.
ef416fc2 3 *
c245abfe 4 * Copyright 2007-2017 by Apple Inc.
7e86f2f6 5 * Copyright 1997-2007 by Easy Software Products.
ef416fc2 6 *
e3101897 7 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
ef416fc2 8 */
9
10/*
11 * Include necessary headers...
12 */
13
71e16022 14#include "cups-private.h"
ef416fc2 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 */
fa73b229 23#ifdef HAVE_COREFOUNDATION_H
24# include <CoreFoundation/CoreFoundation.h>
25#endif /* HAVE_COREFOUNDATION_H */
ef416fc2 26
27
d6ae789d 28/*
29 * Local globals...
30 */
31
6d2f911b 32static _cups_mutex_t lang_mutex = _CUPS_MUTEX_INITIALIZER;
d6ae789d 33 /* Mutex to control access to cache */
d6ae789d 34static cups_lang_t *lang_cache = NULL;
35 /* Language string cache */
ef416fc2 36static 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",
cc754834
MS
45 "iso-8859-15", "cp874",
46 "cp1250", "cp1251",
47 "cp1252", "cp1253",
48 "cp1254", "cp1255",
49 "cp1256", "cp1257",
50 "cp1258", "koi8-r",
ef416fc2 51 "koi8-u", "iso-8859-11",
a2326b5b 52 "iso-8859-16", "mac",
ef416fc2 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",
cc754834
MS
70 "cp932", "cp936",
71 "cp949", "cp950",
72 "cp1361", "unknown",
ef416fc2 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",
4b3f67ff 103 "euc-kr", "euc-tw",
23ee1efa 104 "shift_jisx0213"
ef416fc2 105 };
106
0a682745
MS
107#ifdef __APPLE__
108typedef struct
109{
110 const char * const language; /* Language ID */
111 const char * const locale; /* Locale ID */
112} _apple_language_locale_t;
113
114static const _apple_language_locale_t apple_language_locale[] =
454708a4
MS
115{ /* Language to locale ID LUT */
116 { "en", "en_US" },
117 { "nb", "no" },
118 { "nb_NO", "no" },
119 { "zh-Hans", "zh_CN" },
2650d637 120 { "zh_HANS", "zh_CN" },
454708a4 121 { "zh-Hant", "zh_TW" },
2650d637 122 { "zh_HANT", "zh_TW" },
454708a4 123 { "zh-Hant_CN", "zh_TW" }
0a682745
MS
124};
125#endif /* __APPLE__ */
126
127
128/*
129 * Local functions...
130 */
131
82f97232 132
0a682745
MS
133#ifdef __APPLE__
134static const char *appleLangDefault(void);
0837b7e8 135# ifdef CUPS_BUNDLEDIR
82f97232
MS
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 */
143static cups_array_t *appleMessageLoad(const char *locale)
144 CF_RETURNS_RETAINED;
0837b7e8 145# endif /* CUPS_BUNDLEDIR */
0a682745
MS
146#endif /* __APPLE__ */
147static cups_lang_t *cups_cache_lookup(const char *name,
148 cups_encoding_t encoding);
149static int cups_message_compare(_cups_message_t *m1,
150 _cups_message_t *m2);
0837b7e8 151static void cups_message_free(_cups_message_t *m);
9c80ffa2 152static void cups_message_load(cups_lang_t *lang);
0a682745
MS
153static void cups_unquote(char *d, const char *s);
154
155
156#ifdef __APPLE__
157/*
0837b7e8
MS
158 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
159 * locale ID.
0a682745
MS
160 */
161
162const 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] = '_';
7e86f2f6
MS
201 language[3] = (char)toupper(language[3] & 255);
202 language[4] = (char)toupper(language[4] & 255);
0a682745
MS
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 {
7e86f2f6 227 CFStringGetCString(langid, language, (CFIndex)langsize, kCFStringEncodingASCII);
0a682745
MS
228 CFRelease(langid);
229 }
230
231 CFRelease(localeid);
232 }
233
234 /*
235 * Return what we got...
236 */
237
238 return (language);
239}
454708a4
MS
240
241
242/*
243 * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID.
244 */
245
246const 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 */
4fac8376
MS
253#ifdef DEBUG
254 char temp[1024]; /* Temporary string */
454708a4
MS
255
256
4fac8376
MS
257 if (!CFStringGetCString(languageName, temp, (CFIndex)sizeof(temp), kCFStringEncodingASCII))
258 temp[0] = '\0';
259
41c0a573 260 DEBUG_printf(("_cupsAppleLocale(languageName=%p(%s), locale=%p, localsize=%d)", (void *)languageName, temp, (void *)locale, (int)localesize));
4fac8376
MS
261#endif /* DEBUG */
262
454708a4
MS
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
4fac8376
MS
274 DEBUG_printf(("_cupsAppleLocale: locale=\"%s\"", locale));
275
454708a4
MS
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 {
2650d637
MS
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] == '-')))
454708a4 291 {
4fac8376 292 DEBUG_printf(("_cupsAppleLocale: Updating locale to \"%s\".", apple_language_locale[i].locale));
454708a4
MS
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)
4fac8376
MS
309 {
310 DEBUG_puts("_cupsAppleLocale: Returning NULL.");
454708a4 311 return (NULL);
4fac8376 312 }
454708a4
MS
313
314 /*
315 * Convert language subtag into region subtag...
316 */
317
318 if (locale[2] == '-')
319 locale[2] = '_';
fdc80a91
MS
320 else if (locale[3] == '-')
321 locale[3] = '_';
454708a4
MS
322
323 if (!strchr(locale, '.'))
324 strlcat(locale, ".UTF-8", localesize);
325
4fac8376
MS
326 DEBUG_printf(("_cupsAppleLocale: Returning \"%s\".", locale));
327
454708a4
MS
328 return (locale);
329}
0a682745
MS
330#endif /* __APPLE__ */
331
ef416fc2 332
333/*
334 * '_cupsEncodingName()' - Return the character encoding name string
335 * for the given encoding enumeration.
336 */
337
338const char * /* O - Character encoding */
339_cupsEncodingName(
340 cups_encoding_t encoding) /* I - Encoding value */
341{
7e86f2f6
MS
342 if (encoding < CUPS_US_ASCII ||
343 encoding >= (cups_encoding_t)(sizeof(lang_encodings) / sizeof(lang_encodings[0])))
c7017ecc
MS
344 {
345 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
346 encoding, lang_encodings[0]));
ef416fc2 347 return (lang_encodings[0]);
c7017ecc 348 }
ef416fc2 349 else
c7017ecc
MS
350 {
351 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
352 encoding, lang_encodings[encoding]));
ef416fc2 353 return (lang_encodings[encoding]);
c7017ecc 354 }
ef416fc2 355}
356
357
358/*
359 * 'cupsLangDefault()' - Return the default language.
360 */
361
362cups_lang_t * /* O - Language data */
363cupsLangDefault(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
374const char * /* O - Character encoding */
375cupsLangEncoding(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
388void
389cupsLangFlush(void)
390{
391 cups_lang_t *lang, /* Current language */
392 *next; /* Next language */
ef416fc2 393
394
395 /*
396 * Free all languages in the cache...
397 */
398
6d2f911b 399 _cupsMutexLock(&lang_mutex);
d6ae789d 400
401 for (lang = lang_cache; lang != NULL; lang = next)
ef416fc2 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
d6ae789d 417 lang_cache = NULL;
418
6d2f911b 419 _cupsMutexUnlock(&lang_mutex);
ef416fc2 420}
421
422
423/*
424 * 'cupsLangFree()' - Free language data.
425 *
5a738aea 426 * This does not actually free anything; use @link cupsLangFlush@ for that.
ef416fc2 427 */
428
429void
430cupsLangFree(cups_lang_t *lang) /* I - Language to free */
431{
6d2f911b 432 _cupsMutexLock(&lang_mutex);
d6ae789d 433
ef416fc2 434 if (lang != NULL && lang->used > 0)
435 lang->used --;
d6ae789d 436
6d2f911b 437 _cupsMutexUnlock(&lang_mutex);
ef416fc2 438}
439
440
441/*
442 * 'cupsLangGet()' - Get a language.
443 */
444
445cups_lang_t * /* O - Language data */
446cupsLangGet(const char *language) /* I - Language or locale */
447{
448 int i; /* Looping var */
480ef0fe 449#ifndef __APPLE__
450 char locale[255]; /* Copy of locale name */
451#endif /* !__APPLE__ */
452 char langname[16], /* Requested language name */
ef416fc2 453 country[16], /* Country code */
454 charset[16], /* Character set */
ef416fc2 455 *csptr, /* Pointer to CODESET string */
ef416fc2 456 *ptr, /* Pointer into language/charset */
0837b7e8 457 real[48]; /* Real language name */
ef416fc2 458 cups_encoding_t encoding; /* Encoding to use */
459 cups_lang_t *lang; /* Current language... */
ef416fc2 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",
bd7854cb 469 "ISO885916", "MACROMAN", "", "",
ef416fc2 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
4b3f67ff
MS
498 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
499 "SHIFT_JISX0213"
ef416fc2 500 };
501
502
f11a948a 503 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language));
ef416fc2 504
505#ifdef __APPLE__
757d2cad 506 /*
507 * Set the character set to UTF-8...
508 */
509
5a9febac 510 strlcpy(charset, "UTF8", sizeof(charset));
757d2cad 511
ef416fc2 512 /*
c8fef167 513 * Apple's setlocale doesn't give us the user's localization
ef416fc2 514 * preference so we have to look it up this way...
515 */
516
c9fc04c6
MS
517 if (!language)
518 {
b0f6947b 519 if (!getenv("SOFTWARE") || (language = getenv("LANG")) == NULL)
c9fc04c6
MS
520 language = appleLangDefault();
521
f11a948a 522 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language));
c9fc04c6 523 }
757d2cad 524
ef416fc2 525#else
757d2cad 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)
ef416fc2 539 {
540 /*
541 * First see if the locale has been set; if it is still "C" or
757d2cad 542 * "POSIX", use the environment to get the default...
ef416fc2 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
f11a948a 551 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr));
ef416fc2 552
553 if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
ef416fc2 554 {
757d2cad 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 ++)
7cf5915e 571 if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr))
757d2cad 572 *ptr++ = *csptr;
573
574 *ptr = '\0';
575 }
757d2cad 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";
ef416fc2 585 }
ef416fc2 586
587 if (ptr)
588 {
589 strlcpy(locale, ptr, sizeof(locale));
590 language = locale;
591
a41f09e2
MS
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
f11a948a 599 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language));
ef416fc2 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 /*
757d2cad 612 * Switch to the POSIX ("C") locale...
ef416fc2 613 */
614
757d2cad 615 language = "C";
ef416fc2 616 }
617
ef416fc2 618#ifdef CODESET
619 /*
620 * On systems that support the nl_langinfo(CODESET) call, use
621 * this value as the character set...
622 */
623
757d2cad 624 if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
ef416fc2 625 {
626 /*
627 * Copy all of the letters and numbers in the CODESET string...
628 */
629
630 for (ptr = charset; *csptr; csptr ++)
7cf5915e 631 if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1))
ef416fc2 632 *ptr++ = *csptr;
633
634 *ptr = '\0';
635
f11a948a 636 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
e07d4801 637 "nl_langinfo(CODESET)...", charset));
ef416fc2 638 }
639#endif /* CODESET */
640
07725fee 641 /*
642 * If we don't have a character set by now, default to UTF-8...
643 */
644
645 if (!charset[0])
5a9febac 646 strlcpy(charset, "UTF8", sizeof(charset));
07725fee 647
ef416fc2 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"))
5a9febac 661 strlcpy(langname, "C", sizeof(langname));
ef416fc2 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))
7e86f2f6 672 *ptr++ = (char)tolower(*language & 255);
ef416fc2 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))
7e86f2f6 686 *ptr++ = (char)toupper(*language & 255);
ef416fc2 687
688 *ptr = '\0';
2793a478
MS
689
690 /*
691 * Map Chinese region codes to legacy country codes.
692 */
693
6579c998 694 if (!strcmp(language, "zh") && !strcmp(country, "HANS"))
2793a478 695 strlcpy(country, "CN", sizeof(country));
6579c998 696 if (!strcmp(language, "zh") && !strcmp(country, "HANT"))
2793a478 697 strlcpy(country, "TW", sizeof(country));
ef416fc2 698 }
699
700 if (*language == '.' && !charset[0])
701 {
702 /*
703 * Copy the encoding...
704 */
705
706 for (language ++, ptr = charset; *language; language ++)
7cf5915e 707 if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1))
7e86f2f6 708 *ptr++ = (char)toupper(*language & 255);
ef416fc2 709
710 *ptr = '\0';
711 }
712
713 /*
714 * Force a POSIX locale for an invalid language name...
715 */
716
fdc80a91 717 if (strlen(langname) != 2 && strlen(langname) != 3)
ef416fc2 718 {
5a9febac 719 strlcpy(langname, "C", sizeof(langname));
ef416fc2 720 country[0] = '\0';
721 charset[0] = '\0';
722 }
723 }
724
f11a948a 725 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
ef416fc2 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 {
bd7854cb 736 for (i = 0;
737 i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
738 i ++)
88f9aafc 739 if (!_cups_strcasecmp(charset, locale_encodings[i]))
ef416fc2 740 {
741 encoding = (cups_encoding_t)i;
742 break;
743 }
8ca02f3c 744
745 if (encoding == CUPS_AUTO_ENCODING)
746 {
747 /*
748 * Map alternate names for various character sets...
749 */
750
88f9aafc
MS
751 if (!_cups_strcasecmp(charset, "iso-2022-jp") ||
752 !_cups_strcasecmp(charset, "sjis"))
8ca02f3c 753 encoding = CUPS_WINDOWS_932;
88f9aafc 754 else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
8ca02f3c 755 encoding = CUPS_WINDOWS_936;
88f9aafc 756 else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
8ca02f3c 757 encoding = CUPS_WINDOWS_949;
88f9aafc 758 else if (!_cups_strcasecmp(charset, "big5"))
8ca02f3c 759 encoding = CUPS_WINDOWS_950;
760 }
ef416fc2 761 }
762
f11a948a 763 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding,
ef416fc2 764 encoding == CUPS_AUTO_ENCODING ? "auto" :
765 lang_encodings[encoding]));
766
767 /*
768 * See if we already have this language/country loaded...
769 */
770
bd7854cb 771 if (country[0])
bd7854cb 772 snprintf(real, sizeof(real), "%s_%s", langname, country);
bd7854cb 773 else
5a9febac 774 strlcpy(real, langname, sizeof(real));
d6ae789d 775
6d2f911b 776 _cupsMutexLock(&lang_mutex);
d6ae789d 777
c0e1af83 778 if ((lang = cups_cache_lookup(real, encoding)) != NULL)
d6ae789d 779 {
6d2f911b 780 _cupsMutexUnlock(&lang_mutex);
d6ae789d 781
f11a948a 782 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real));
c9fc04c6 783
d6ae789d 784 return (lang);
785 }
ef416fc2 786
ef416fc2 787 /*
788 * See if there is a free language available; if so, use that
789 * record...
790 */
791
d6ae789d 792 for (lang = lang_cache; lang != NULL; lang = lang->next)
ef416fc2 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)
d6ae789d 803 {
6d2f911b 804 _cupsMutexUnlock(&lang_mutex);
d6ae789d 805
ef416fc2 806 return (NULL);
d6ae789d 807 }
ef416fc2 808
d6ae789d 809 lang->next = lang_cache;
810 lang_cache = lang;
ef416fc2 811 }
bd7854cb 812 else
813 {
814 /*
815 * Free all old strings as needed...
816 */
ef416fc2 817
bd7854cb 818 _cupsMessageFree(lang->strings);
9c80ffa2 819 lang->strings = NULL;
bd7854cb 820 }
ef416fc2 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
ef416fc2 834 /*
835 * Return...
836 */
837
6d2f911b 838 _cupsMutexUnlock(&lang_mutex);
d6ae789d 839
ef416fc2 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
851const char * /* O - Localized message */
852_cupsLangString(cups_lang_t *lang, /* I - Language */
853 const char *message) /* I - Message */
854{
6d2f911b
MS
855 const char *s; /* Localized message */
856
a9357c9d
MS
857
858 DEBUG_printf(("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang, message));
859
ef416fc2 860 /*
861 * Range check input...
862 */
863
37e7e6e0 864 if (!lang || !message || !*message)
ef416fc2 865 return (message);
866
6d2f911b 867 _cupsMutexLock(&lang_mutex);
d6ae789d 868
9c80ffa2
MS
869 /*
870 * Load the message catalog if needed...
871 */
872
873 if (!lang->strings)
874 cups_message_load(lang);
875
6d2f911b 876 s = _cupsMessageLookup(lang->strings, message);
d6ae789d 877
6d2f911b 878 _cupsMutexUnlock(&lang_mutex);
d6ae789d 879
6d2f911b 880 return (s);
ef416fc2 881}
882
883
884/*
885 * '_cupsMessageFree()' - Free a messages array.
886 */
887
888void
889_cupsMessageFree(cups_array_t *a) /* I - Message array */
890{
0837b7e8
MS
891#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
892 /*
893 * Release the cups.strings dictionary as needed...
894 */
ef416fc2 895
0837b7e8
MS
896 if (cupsArrayUserData(a))
897 CFRelease((CFDictionaryRef)cupsArrayUserData(a));
898#endif /* __APPLE__ && CUPS_BUNDLEDIR */
ef416fc2 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
912cups_array_t * /* O - New message array */
8b116e60 913_cupsMessageLoad(const char *filename, /* I - Message catalog to load */
0837b7e8 914 int unquote) /* I - Unescape \foo in strings? */
ef416fc2 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 */
7e86f2f6
MS
922 size_t length, /* Length of combined strings */
923 ptrlen; /* Length of string */
ef416fc2 924
925
e07d4801 926 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename));
bd7854cb 927
ef416fc2 928 /*
929 * Create an array to hold the messages...
930 */
931
a29fd7dd 932 if ((a = _cupsMessageNew(NULL)) == NULL)
c9fc04c6 933 {
e07d4801 934 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
ef416fc2 935 return (NULL);
c9fc04c6 936 }
ef416fc2 937
938 /*
939 * Open the message catalog file...
940 */
941
942 if ((fp = cupsFileOpen(filename, "r")) == NULL)
c9fc04c6 943 {
e07d4801 944 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
c9fc04c6 945 strerror(errno)));
ef416fc2 946 return (a);
c9fc04c6 947 }
ef416fc2 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 *
bd7854cb 957 * The ID and localized text can span multiple lines using the form:
ef416fc2 958 *
bd7854cb 959 * msgid ""
960 * "some long text"
961 * msgstr ""
962 * "localized text spanning "
ef416fc2 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 */
c8fef167 989
ef416fc2 990 if ((ptr = strchr(s, '\"')) == NULL)
991 continue;
992
993 ptr ++;
994
995 /*
996 * Unquote the text...
997 */
998
8b116e60
MS
999 if (unquote)
1000 cups_unquote(ptr, ptr);
ef416fc2 1001
1002 /*
1003 * Create or add to a message...
1004 */
1005
1006 if (!strncmp(s, "msgid", 5))
1007 {
bd7854cb 1008 /*
1009 * Add previous message as needed...
1010 */
1011
1012 if (m)
82cc1f9a
MS
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 }
bd7854cb 1030
1031 /*
1032 * Create a new message with the given msgid string...
1033 */
1034
ef416fc2 1035 if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
1036 {
1037 cupsFileClose(fp);
1038 return (a);
1039 }
1040
91c84a35
MS
1041 if ((m->id = strdup(ptr)) == NULL)
1042 {
1043 free(m);
1044 cupsFileClose(fp);
1045 return (a);
1046 }
ef416fc2 1047 }
bd7854cb 1048 else if (s[0] == '\"' && m)
ef416fc2 1049 {
bd7854cb 1050 /*
1051 * Append to current string...
1052 */
ef416fc2 1053
7e86f2f6 1054 length = strlen(m->str ? m->str : m->id);
5a9febac 1055 ptrlen = strlen(ptr);
ef416fc2 1056
7e86f2f6 1057 if ((temp = realloc(m->str ? m->str : m->id, length + ptrlen + 1)) == NULL)
bd7854cb 1058 {
82cc1f9a
MS
1059 if (m->str)
1060 free(m->str);
1061 free(m->id);
1062 free(m);
1063
bd7854cb 1064 cupsFileClose(fp);
1065 return (a);
1066 }
ef416fc2 1067
bd7854cb 1068 if (m->str)
1069 {
ef416fc2 1070 /*
bd7854cb 1071 * Copy the new portion to the end of the msgstr string - safe
5a9febac 1072 * to use memcpy because the buffer is allocated to the correct
bd7854cb 1073 * size...
ef416fc2 1074 */
1075
bd7854cb 1076 m->str = temp;
1077
5a9febac 1078 memcpy(m->str + length, ptr, ptrlen + 1);
ef416fc2 1079 }
1080 else
1081 {
1082 /*
bd7854cb 1083 * Copy the new portion to the end of the msgid string - safe
5a9febac 1084 * to use memcpy because the buffer is allocated to the correct
bd7854cb 1085 * size...
ef416fc2 1086 */
1087
bd7854cb 1088 m->id = temp;
1089
5a9febac 1090 memcpy(m->id + length, ptr, ptrlen + 1);
ef416fc2 1091 }
1092 }
bd7854cb 1093 else if (!strncmp(s, "msgstr", 6) && m)
1094 {
1095 /*
1096 * Set the string...
1097 */
1098
91c84a35
MS
1099 if ((m->str = strdup(ptr)) == NULL)
1100 {
82cc1f9a
MS
1101 free(m->id);
1102 free(m);
1103
91c84a35
MS
1104 cupsFileClose(fp);
1105 return (a);
1106 }
bd7854cb 1107 }
ef416fc2 1108 }
1109
bd7854cb 1110 /*
1111 * Add the last message string to the array as needed...
1112 */
1113
1114 if (m)
82cc1f9a
MS
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 }
bd7854cb 1132
ef416fc2 1133 /*
1134 * Close the message catalog file and return the new array...
1135 */
1136
1137 cupsFileClose(fp);
1138
e07d4801 1139 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
c9fc04c6
MS
1140 cupsArrayCount(a)));
1141
ef416fc2 1142 return (a);
1143}
1144
1145
1146/*
1147 * '_cupsMessageLookup()' - Lookup a message string.
1148 */
1149
1150const 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
a9357c9d
MS
1158 DEBUG_printf(("_cupsMessageLookup(a=%p, m=\"%s\")", (void *)a, m));
1159
ef416fc2 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
0837b7e8
MS
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);
c8fef167
MS
1192
1193 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...",
1194 m, buffer));
0837b7e8
MS
1195 }
1196 else
c8fef167 1197 {
0837b7e8
MS
1198 match->str = strdup(m);
1199
c8fef167
MS
1200 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m));
1201 }
1202
0837b7e8 1203 cupsArrayAdd(a, match);
e60ec91f
MS
1204
1205 if (cfm)
1206 CFRelease(cfm);
0837b7e8
MS
1207 }
1208#endif /* __APPLE__ && CUPS_BUNDLEDIR */
1209
ef416fc2 1210 if (match && match->str)
1211 return (match->str);
1212 else
1213 return (m);
1214}
1215
1216
a29fd7dd
MS
1217/*
1218 * '_cupsMessageNew()' - Make a new message catalog array.
1219 */
1220
1221cups_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
ef416fc2 1231#ifdef __APPLE__
ef416fc2 1232/*
1233 * 'appleLangDefault()' - Get the default locale string.
1234 */
1235
1236static const char * /* O - Locale string */
1237appleLangDefault(void)
1238{
db1f069b
MS
1239 CFBundleRef bundle; /* Main bundle (if any) */
1240 CFArrayRef bundleList; /* List of localizations in bundle */
b2656447 1241 CFPropertyListRef localizationList = NULL;
ef416fc2 1242 /* List of localization data */
1243 CFStringRef languageName; /* Current name */
26d47ec6 1244 char *lang; /* LANG environment variable */
ef416fc2 1245 _cups_globals_t *cg = _cupsGlobals();
1246 /* Pointer to library globals */
1247
1248
b0f6947b
MS
1249 DEBUG_puts("2appleLangDefault()");
1250
ef416fc2 1251 /*
1252 * Only do the lookup and translation the first time.
1253 */
1254
1255 if (!cg->language[0])
1256 {
b0f6947b 1257 if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL)
db1f069b 1258 {
94436c5a 1259 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang));
26d47ec6 1260 strlcpy(cg->language, lang, sizeof(cg->language));
db1f069b
MS
1261 return (cg->language);
1262 }
1263 else if ((bundle = CFBundleGetMainBundle()) != NULL &&
1264 (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
ef416fc2 1265 {
b2656447
MS
1266 CFURLRef resources = CFBundleCopyResourcesDirectoryURL(bundle);
1267
94436c5a
MS
1268 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
1269
b2656447
MS
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
36474350 1281 CFStringGetCString(cfpath, path, sizeof(path), kCFStringEncodingUTF8);
b2656447
MS
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.");
26d47ec6 1297
db1f069b
MS
1298 CFRelease(bundleList);
1299 }
b2656447
MS
1300
1301 if (!localizationList)
94436c5a
MS
1302 {
1303 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
1304
db1f069b
MS
1305 localizationList =
1306 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1307 kCFPreferencesCurrentApplication);
94436c5a 1308 }
db1f069b
MS
1309
1310 if (localizationList)
1311 {
94436c5a
MS
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
db1f069b
MS
1320 if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
1321 CFArrayGetCount(localizationList) > 0)
ef416fc2 1322 {
db1f069b
MS
1323 languageName = CFArrayGetValueAtIndex(localizationList, 0);
1324
1325 if (languageName &&
1326 CFGetTypeID(languageName) == CFStringGetTypeID())
26d47ec6 1327 {
454708a4 1328 if (_cupsAppleLocale(languageName, cg->language, sizeof(cg->language)))
94436c5a 1329 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
db1f069b 1330 cg->language));
94436c5a 1331 else
454708a4 1332 DEBUG_puts("3appleLangDefault: Unable to get locale.");
26d47ec6 1333 }
26d47ec6 1334 }
db1f069b
MS
1335
1336 CFRelease(localizationList);
ef416fc2 1337 }
c8fef167 1338
ef416fc2 1339 /*
1340 * If we didn't find the language, default to en_US...
1341 */
1342
1343 if (!cg->language[0])
94436c5a
MS
1344 {
1345 DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
ef416fc2 1346 strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
94436c5a 1347 }
ef416fc2 1348 }
36474350
MS
1349 else
1350 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg->language));
ef416fc2 1351
1352 /*
1353 * Return the cached locale...
1354 */
1355
1356 return (cg->language);
1357}
0837b7e8
MS
1358
1359
1360# ifdef CUPS_BUNDLEDIR
1361/*
1362 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1363 */
1364
1365static cups_array_t * /* O - Message catalog */
1366appleMessageLoad(const char *locale) /* I - Locale ID */
1367{
1368 char filename[1024], /* Path to cups.strings file */
36ac4374 1369 applelang[256], /* Apple language ID */
fdc80a91 1370 baselang[4]; /* Base language */
0837b7e8 1371 CFURLRef url; /* URL to cups.strings file */
c8fef167
MS
1372 CFReadStreamRef stream = NULL; /* File stream */
1373 CFPropertyListRef plist = NULL; /* Localization file */
1374#ifdef DEBUG
a9357c9d
MS
1375 const char *cups_strings = getenv("CUPS_STRINGS");
1376 /* Test strings file */
c8fef167
MS
1377 CFErrorRef error = NULL; /* Error when opening file */
1378#endif /* DEBUG */
1379
1380
1381 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale));
0837b7e8
MS
1382
1383 /*
1384 * Load the cups.strings file...
1385 */
1386
a9357c9d
MS
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
0837b7e8
MS
1396 snprintf(filename, sizeof(filename),
1397 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings",
1398 _cupsAppleLanguage(locale, applelang, sizeof(applelang)));
88da9d73
MS
1399
1400 if (access(filename, 0))
1401 {
1402 /*
1403 * <rdar://problem/22086642>
1404 *
1405 * Try with original locale string...
1406 */
1407
a9357c9d 1408 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
88da9d73
MS
1409 snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
1410 }
1411
b052deed
MS
1412 if (access(filename, 0))
1413 {
1414 /*
1415 * <rdar://problem/25292403>
1416 *
1417 * Try with just the language code...
1418 */
1419
a9357c9d
MS
1420 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
1421
b052deed 1422 strlcpy(baselang, locale, sizeof(baselang));
fdc80a91
MS
1423 if (baselang[3] == '-' || baselang[3] == '_')
1424 baselang[3] = '\0';
1425
b052deed
MS
1426 snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", baselang);
1427 }
1428
c8fef167
MS
1429 if (access(filename, 0))
1430 {
1431 /*
1432 * Try alternate lproj directory names...
1433 */
1434
a9357c9d
MS
1435 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
1436
c8fef167
MS
1437 if (!strncmp(locale, "en", 2))
1438 locale = "English";
b052deed
MS
1439 else if (!strncmp(locale, "nb", 2))
1440 locale = "no";
1441 else if (!strncmp(locale, "nl", 2))
c8fef167
MS
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";
fdc80a91 1453 else if (!strcmp(locale, "zh_HK") || !strncasecmp(locale, "zh-Hant", 7) || !strncasecmp(locale, "zh_Hant", 7))
bd3e2e22
MS
1454 {
1455 /*
1456 * <rdar://problem/22130168>
14fdcd7a 1457 * <rdar://problem/27245567>
bd3e2e22
MS
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 }
36ac4374
MS
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));
997db404
MS
1474 if (baselang[2] == '-' || baselang[2] == '_')
1475 baselang[2] = '\0';
fdc80a91 1476
36ac4374
MS
1477 locale = baselang;
1478 }
c8fef167
MS
1479
1480 snprintf(filename, sizeof(filename),
1481 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
c8fef167
MS
1482 }
1483
a9357c9d
MS
1484 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename));
1485
c8fef167
MS
1486 url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
1487 (UInt8 *)filename,
7e86f2f6 1488 (CFIndex)strlen(filename), false);
c8fef167
MS
1489 if (url)
1490 {
1491 stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
1492 if (stream)
1493 {
82f97232
MS
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
c8fef167
MS
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
82f97232 1516 CFRelease(msg);
c8fef167
MS
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 }
0837b7e8 1537
c8fef167
MS
1538 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url, stream,
1539 plist));
0837b7e8
MS
1540
1541 /*
1542 * Create and return an empty array to act as a cache for messages, passing the
c8fef167 1543 * plist as the user data.
0837b7e8
MS
1544 */
1545
a29fd7dd 1546 return (_cupsMessageNew((void *)plist));
0837b7e8
MS
1547}
1548# endif /* CUPS_BUNDLEDIR */
ef416fc2 1549#endif /* __APPLE__ */
1550
1551
1552/*
1553 * 'cups_cache_lookup()' - Lookup a language in the cache...
1554 */
1555
1556static cups_lang_t * /* O - Language data or NULL */
0837b7e8
MS
1557cups_cache_lookup(
1558 const char *name, /* I - Name of locale */
1559 cups_encoding_t encoding) /* I - Encoding of locale */
ef416fc2 1560{
1561 cups_lang_t *lang; /* Current language */
1562
1563
e07d4801 1564 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name,
ef416fc2 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
d6ae789d 1572 for (lang = lang_cache; lang != NULL; lang = lang->next)
ef416fc2 1573 {
e07d4801 1574 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
807315e6 1575 "encoding=%d(%s)", (void *)lang, lang->language, lang->encoding,
ef416fc2 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
e07d4801 1583 DEBUG_puts("8cups_cache_lookup: returning match!");
ef416fc2 1584
1585 return (lang);
1586 }
1587 }
1588
e07d4801 1589 DEBUG_puts("8cups_cache_lookup: returning NULL!");
ef416fc2 1590
1591 return (NULL);
1592}
1593
1594
1595/*
1596 * 'cups_message_compare()' - Compare two messages.
1597 */
1598
1599static int /* O - Result of comparison */
1600cups_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
0837b7e8
MS
1608/*
1609 * 'cups_message_free()' - Free a message.
1610 */
1611
1612static void
1613cups_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
9c80ffa2
MS
1625/*
1626 * 'cups_message_load()' - Load the message catalog for a language.
1627 */
1628
1629static void
1630cups_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
ef416fc2 1675/*
1676 * 'cups_unquote()' - Unquote characters in strings...
1677 */
1678
1679static void
1680cups_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 }
d09495fa 1697
1698 d ++;
ef416fc2 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}