]> git.ipfire.org Git - thirdparty/cups.git/blame - cups/language.c
Merge pull request #5184 from albert-astals-cid-kdab/master
[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 */
89550f3f 143static cups_array_t *appleMessageLoad(const char *locale) CF_RETURNS_RETAINED;
0837b7e8 144# endif /* CUPS_BUNDLEDIR */
0a682745 145#endif /* __APPLE__ */
89550f3f
MS
146static cups_lang_t *cups_cache_lookup(const char *name, cups_encoding_t encoding);
147static int cups_message_compare(_cups_message_t *m1, _cups_message_t *m2);
0837b7e8 148static void cups_message_free(_cups_message_t *m);
9c80ffa2 149static void cups_message_load(cups_lang_t *lang);
89550f3f 150static int cups_read_strings(cups_file_t *fp, int flags, cups_array_t *a);
0a682745
MS
151static void cups_unquote(char *d, const char *s);
152
153
154#ifdef __APPLE__
155/*
0837b7e8
MS
156 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
157 * locale ID.
0a682745
MS
158 */
159
160const char * /* O - Language ID */
161_cupsAppleLanguage(const char *locale, /* I - Locale ID */
162 char *language,/* I - Language ID buffer */
163 size_t langsize) /* I - Size of language ID buffer */
164{
165 int i; /* Looping var */
166 CFStringRef localeid, /* CF locale identifier */
167 langid; /* CF language identifier */
168
169
170 /*
171 * Copy the locale name and convert, as needed, to the Apple-specific
172 * locale identifier...
173 */
174
175 switch (strlen(locale))
176 {
177 default :
178 /*
179 * Invalid locale...
180 */
181
182 strlcpy(language, "en", langsize);
183 break;
184
185 case 2 :
186 strlcpy(language, locale, langsize);
187 break;
188
189 case 5 :
190 strlcpy(language, locale, langsize);
191
192 if (language[2] == '-')
193 {
194 /*
195 * Convert ll-cc to ll_CC...
196 */
197
198 language[2] = '_';
7e86f2f6
MS
199 language[3] = (char)toupper(language[3] & 255);
200 language[4] = (char)toupper(language[4] & 255);
0a682745
MS
201 }
202 break;
203 }
204
205 for (i = 0;
206 i < (int)(sizeof(apple_language_locale) /
207 sizeof(apple_language_locale[0]));
208 i ++)
209 if (!strcmp(locale, apple_language_locale[i].locale))
210 {
211 strlcpy(language, apple_language_locale[i].language, sizeof(language));
212 break;
213 }
214
215 /*
216 * Attempt to map the locale ID to a language ID...
217 */
218
219 if ((localeid = CFStringCreateWithCString(kCFAllocatorDefault, language,
220 kCFStringEncodingASCII)) != NULL)
221 {
222 if ((langid = CFLocaleCreateCanonicalLanguageIdentifierFromString(
223 kCFAllocatorDefault, localeid)) != NULL)
224 {
7e86f2f6 225 CFStringGetCString(langid, language, (CFIndex)langsize, kCFStringEncodingASCII);
0a682745
MS
226 CFRelease(langid);
227 }
228
229 CFRelease(localeid);
230 }
231
232 /*
233 * Return what we got...
234 */
235
236 return (language);
237}
454708a4
MS
238
239
240/*
241 * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID.
242 */
243
244const char * /* O - Locale */
245_cupsAppleLocale(CFStringRef languageName, /* I - Apple language ID */
246 char *locale, /* I - Buffer for locale */
247 size_t localesize) /* I - Size of buffer */
248{
249 int i; /* Looping var */
250 CFStringRef localeName; /* Locale as a CF string */
4fac8376
MS
251#ifdef DEBUG
252 char temp[1024]; /* Temporary string */
454708a4
MS
253
254
4fac8376
MS
255 if (!CFStringGetCString(languageName, temp, (CFIndex)sizeof(temp), kCFStringEncodingASCII))
256 temp[0] = '\0';
257
41c0a573 258 DEBUG_printf(("_cupsAppleLocale(languageName=%p(%s), locale=%p, localsize=%d)", (void *)languageName, temp, (void *)locale, (int)localesize));
4fac8376
MS
259#endif /* DEBUG */
260
454708a4
MS
261 localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, languageName);
262
263 if (localeName)
264 {
265 /*
266 * Copy the locale name and tweak as needed...
267 */
268
269 if (!CFStringGetCString(localeName, locale, (CFIndex)localesize, kCFStringEncodingASCII))
270 *locale = '\0';
271
4fac8376
MS
272 DEBUG_printf(("_cupsAppleLocale: locale=\"%s\"", locale));
273
454708a4
MS
274 CFRelease(localeName);
275
276 /*
277 * Map new language identifiers to locales...
278 */
279
280 for (i = 0;
281 i < (int)(sizeof(apple_language_locale) /
282 sizeof(apple_language_locale[0]));
283 i ++)
284 {
2650d637
MS
285 size_t len = strlen(apple_language_locale[i].language);
286
287 if (!strcmp(locale, apple_language_locale[i].language) ||
288 (!strncmp(locale, apple_language_locale[i].language, len) && (locale[len] == '_' || locale[len] == '-')))
454708a4 289 {
4fac8376 290 DEBUG_printf(("_cupsAppleLocale: Updating locale to \"%s\".", apple_language_locale[i].locale));
454708a4
MS
291 strlcpy(locale, apple_language_locale[i].locale, localesize);
292 break;
293 }
294 }
295 }
296 else
297 {
298 /*
299 * Just try the Apple language name...
300 */
301
302 if (!CFStringGetCString(languageName, locale, (CFIndex)localesize, kCFStringEncodingASCII))
303 *locale = '\0';
304 }
305
306 if (!*locale)
4fac8376
MS
307 {
308 DEBUG_puts("_cupsAppleLocale: Returning NULL.");
454708a4 309 return (NULL);
4fac8376 310 }
454708a4
MS
311
312 /*
313 * Convert language subtag into region subtag...
314 */
315
316 if (locale[2] == '-')
317 locale[2] = '_';
fdc80a91
MS
318 else if (locale[3] == '-')
319 locale[3] = '_';
454708a4
MS
320
321 if (!strchr(locale, '.'))
322 strlcat(locale, ".UTF-8", localesize);
323
4fac8376
MS
324 DEBUG_printf(("_cupsAppleLocale: Returning \"%s\".", locale));
325
454708a4
MS
326 return (locale);
327}
0a682745
MS
328#endif /* __APPLE__ */
329
ef416fc2 330
331/*
332 * '_cupsEncodingName()' - Return the character encoding name string
333 * for the given encoding enumeration.
334 */
335
336const char * /* O - Character encoding */
337_cupsEncodingName(
338 cups_encoding_t encoding) /* I - Encoding value */
339{
7e86f2f6
MS
340 if (encoding < CUPS_US_ASCII ||
341 encoding >= (cups_encoding_t)(sizeof(lang_encodings) / sizeof(lang_encodings[0])))
c7017ecc
MS
342 {
343 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
344 encoding, lang_encodings[0]));
ef416fc2 345 return (lang_encodings[0]);
c7017ecc 346 }
ef416fc2 347 else
c7017ecc
MS
348 {
349 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
350 encoding, lang_encodings[encoding]));
ef416fc2 351 return (lang_encodings[encoding]);
c7017ecc 352 }
ef416fc2 353}
354
355
356/*
357 * 'cupsLangDefault()' - Return the default language.
358 */
359
360cups_lang_t * /* O - Language data */
361cupsLangDefault(void)
362{
363 return (cupsLangGet(NULL));
364}
365
366
367/*
368 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
369 * for the given language.
370 */
371
372const char * /* O - Character encoding */
373cupsLangEncoding(cups_lang_t *lang) /* I - Language data */
374{
375 if (lang == NULL)
376 return ((char*)lang_encodings[0]);
377 else
378 return ((char*)lang_encodings[lang->encoding]);
379}
380
381
382/*
383 * 'cupsLangFlush()' - Flush all language data out of the cache.
384 */
385
386void
387cupsLangFlush(void)
388{
389 cups_lang_t *lang, /* Current language */
390 *next; /* Next language */
ef416fc2 391
392
393 /*
394 * Free all languages in the cache...
395 */
396
6d2f911b 397 _cupsMutexLock(&lang_mutex);
d6ae789d 398
399 for (lang = lang_cache; lang != NULL; lang = next)
ef416fc2 400 {
401 /*
402 * Free all messages...
403 */
404
405 _cupsMessageFree(lang->strings);
406
407 /*
408 * Then free the language structure itself...
409 */
410
411 next = lang->next;
412 free(lang);
413 }
414
d6ae789d 415 lang_cache = NULL;
416
6d2f911b 417 _cupsMutexUnlock(&lang_mutex);
ef416fc2 418}
419
420
421/*
422 * 'cupsLangFree()' - Free language data.
423 *
5a738aea 424 * This does not actually free anything; use @link cupsLangFlush@ for that.
ef416fc2 425 */
426
427void
428cupsLangFree(cups_lang_t *lang) /* I - Language to free */
429{
6d2f911b 430 _cupsMutexLock(&lang_mutex);
d6ae789d 431
ef416fc2 432 if (lang != NULL && lang->used > 0)
433 lang->used --;
d6ae789d 434
6d2f911b 435 _cupsMutexUnlock(&lang_mutex);
ef416fc2 436}
437
438
439/*
440 * 'cupsLangGet()' - Get a language.
441 */
442
443cups_lang_t * /* O - Language data */
444cupsLangGet(const char *language) /* I - Language or locale */
445{
446 int i; /* Looping var */
480ef0fe 447#ifndef __APPLE__
448 char locale[255]; /* Copy of locale name */
449#endif /* !__APPLE__ */
450 char langname[16], /* Requested language name */
ef416fc2 451 country[16], /* Country code */
452 charset[16], /* Character set */
ef416fc2 453 *csptr, /* Pointer to CODESET string */
ef416fc2 454 *ptr, /* Pointer into language/charset */
0837b7e8 455 real[48]; /* Real language name */
ef416fc2 456 cups_encoding_t encoding; /* Encoding to use */
457 cups_lang_t *lang; /* Current language... */
ef416fc2 458 static const char * const locale_encodings[] =
459 { /* Locale charset names */
460 "ASCII", "ISO88591", "ISO88592", "ISO88593",
461 "ISO88594", "ISO88595", "ISO88596", "ISO88597",
462 "ISO88598", "ISO88599", "ISO885910", "UTF8",
463 "ISO885913", "ISO885914", "ISO885915", "CP874",
464 "CP1250", "CP1251", "CP1252", "CP1253",
465 "CP1254", "CP1255", "CP1256", "CP1257",
466 "CP1258", "KOI8R", "KOI8U", "ISO885911",
bd7854cb 467 "ISO885916", "MACROMAN", "", "",
ef416fc2 468
469 "", "", "", "",
470 "", "", "", "",
471 "", "", "", "",
472 "", "", "", "",
473 "", "", "", "",
474 "", "", "", "",
475 "", "", "", "",
476 "", "", "", "",
477
478 "CP932", "CP936", "CP949", "CP950",
479 "CP1361", "", "", "",
480 "", "", "", "",
481 "", "", "", "",
482 "", "", "", "",
483 "", "", "", "",
484 "", "", "", "",
485 "", "", "", "",
486
487 "", "", "", "",
488 "", "", "", "",
489 "", "", "", "",
490 "", "", "", "",
491 "", "", "", "",
492 "", "", "", "",
493 "", "", "", "",
494 "", "", "", "",
495
4b3f67ff
MS
496 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
497 "SHIFT_JISX0213"
ef416fc2 498 };
499
500
f11a948a 501 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language));
ef416fc2 502
503#ifdef __APPLE__
757d2cad 504 /*
505 * Set the character set to UTF-8...
506 */
507
5a9febac 508 strlcpy(charset, "UTF8", sizeof(charset));
757d2cad 509
ef416fc2 510 /*
c8fef167 511 * Apple's setlocale doesn't give us the user's localization
ef416fc2 512 * preference so we have to look it up this way...
513 */
514
c9fc04c6
MS
515 if (!language)
516 {
b0f6947b 517 if (!getenv("SOFTWARE") || (language = getenv("LANG")) == NULL)
c9fc04c6
MS
518 language = appleLangDefault();
519
f11a948a 520 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language));
c9fc04c6 521 }
757d2cad 522
ef416fc2 523#else
757d2cad 524 /*
525 * Set the charset to "unknown"...
526 */
527
528 charset[0] = '\0';
529
530 /*
531 * Use setlocale() to determine the currently set locale, and then
532 * fallback to environment variables to avoid setting the locale,
533 * since setlocale() is not thread-safe!
534 */
535
536 if (!language)
ef416fc2 537 {
538 /*
539 * First see if the locale has been set; if it is still "C" or
757d2cad 540 * "POSIX", use the environment to get the default...
ef416fc2 541 */
542
543# ifdef LC_MESSAGES
544 ptr = setlocale(LC_MESSAGES, NULL);
545# else
546 ptr = setlocale(LC_ALL, NULL);
547# endif /* LC_MESSAGES */
548
f11a948a 549 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr));
ef416fc2 550
551 if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
ef416fc2 552 {
757d2cad 553 /*
554 * Get the character set from the LC_CTYPE locale setting...
555 */
556
557 if ((ptr = getenv("LC_CTYPE")) == NULL)
558 if ((ptr = getenv("LC_ALL")) == NULL)
559 if ((ptr = getenv("LANG")) == NULL)
560 ptr = "en_US";
561
562 if ((csptr = strchr(ptr, '.')) != NULL)
563 {
564 /*
565 * Extract the character set from the environment...
566 */
567
568 for (ptr = charset, csptr ++; *csptr; csptr ++)
7cf5915e 569 if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr))
757d2cad 570 *ptr++ = *csptr;
571
572 *ptr = '\0';
573 }
757d2cad 574
575 /*
576 * Get the locale for messages from the LC_MESSAGES locale setting...
577 */
578
579 if ((ptr = getenv("LC_MESSAGES")) == NULL)
580 if ((ptr = getenv("LC_ALL")) == NULL)
581 if ((ptr = getenv("LANG")) == NULL)
582 ptr = "en_US";
ef416fc2 583 }
ef416fc2 584
585 if (ptr)
586 {
587 strlcpy(locale, ptr, sizeof(locale));
588 language = locale;
589
a41f09e2
MS
590 /*
591 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
592 */
593
594 if (!strncmp(locale, "nb", 2))
595 locale[1] = 'o';
596
f11a948a 597 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language));
ef416fc2 598 }
599 }
600#endif /* __APPLE__ */
601
602 /*
603 * If "language" is NULL at this point, then chances are we are using
604 * a language that is not installed for the base OS.
605 */
606
607 if (!language)
608 {
609 /*
757d2cad 610 * Switch to the POSIX ("C") locale...
ef416fc2 611 */
612
757d2cad 613 language = "C";
ef416fc2 614 }
615
ef416fc2 616#ifdef CODESET
617 /*
618 * On systems that support the nl_langinfo(CODESET) call, use
619 * this value as the character set...
620 */
621
757d2cad 622 if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
ef416fc2 623 {
624 /*
625 * Copy all of the letters and numbers in the CODESET string...
626 */
627
628 for (ptr = charset; *csptr; csptr ++)
7cf5915e 629 if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1))
ef416fc2 630 *ptr++ = *csptr;
631
632 *ptr = '\0';
633
f11a948a 634 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
e07d4801 635 "nl_langinfo(CODESET)...", charset));
ef416fc2 636 }
637#endif /* CODESET */
638
07725fee 639 /*
640 * If we don't have a character set by now, default to UTF-8...
641 */
642
643 if (!charset[0])
5a9febac 644 strlcpy(charset, "UTF8", sizeof(charset));
07725fee 645
ef416fc2 646 /*
647 * Parse the language string passed in to a locale string. "C" is the
648 * standard POSIX locale and is copied unchanged. Otherwise the
649 * language string is converted from ll-cc[.charset] (language-country)
650 * to ll_CC[.CHARSET] to match the file naming convention used by all
651 * POSIX-compliant operating systems. Invalid language names are mapped
652 * to the POSIX locale.
653 */
654
655 country[0] = '\0';
656
657 if (language == NULL || !language[0] ||
658 !strcmp(language, "POSIX"))
5a9febac 659 strlcpy(langname, "C", sizeof(langname));
ef416fc2 660 else
661 {
662 /*
663 * Copy the parts of the locale string over safely...
664 */
665
666 for (ptr = langname; *language; language ++)
667 if (*language == '_' || *language == '-' || *language == '.')
668 break;
669 else if (ptr < (langname + sizeof(langname) - 1))
7e86f2f6 670 *ptr++ = (char)tolower(*language & 255);
ef416fc2 671
672 *ptr = '\0';
673
674 if (*language == '_' || *language == '-')
675 {
676 /*
677 * Copy the country code...
678 */
679
680 for (language ++, ptr = country; *language; language ++)
681 if (*language == '.')
682 break;
683 else if (ptr < (country + sizeof(country) - 1))
7e86f2f6 684 *ptr++ = (char)toupper(*language & 255);
ef416fc2 685
686 *ptr = '\0';
2793a478
MS
687
688 /*
689 * Map Chinese region codes to legacy country codes.
690 */
691
6579c998 692 if (!strcmp(language, "zh") && !strcmp(country, "HANS"))
2793a478 693 strlcpy(country, "CN", sizeof(country));
6579c998 694 if (!strcmp(language, "zh") && !strcmp(country, "HANT"))
2793a478 695 strlcpy(country, "TW", sizeof(country));
ef416fc2 696 }
697
698 if (*language == '.' && !charset[0])
699 {
700 /*
701 * Copy the encoding...
702 */
703
704 for (language ++, ptr = charset; *language; language ++)
7cf5915e 705 if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1))
7e86f2f6 706 *ptr++ = (char)toupper(*language & 255);
ef416fc2 707
708 *ptr = '\0';
709 }
710
711 /*
712 * Force a POSIX locale for an invalid language name...
713 */
714
fdc80a91 715 if (strlen(langname) != 2 && strlen(langname) != 3)
ef416fc2 716 {
5a9febac 717 strlcpy(langname, "C", sizeof(langname));
ef416fc2 718 country[0] = '\0';
719 charset[0] = '\0';
720 }
721 }
722
f11a948a 723 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
ef416fc2 724 langname, country, charset));
725
726 /*
727 * Figure out the desired encoding...
728 */
729
730 encoding = CUPS_AUTO_ENCODING;
731
732 if (charset[0])
733 {
bd7854cb 734 for (i = 0;
735 i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
736 i ++)
88f9aafc 737 if (!_cups_strcasecmp(charset, locale_encodings[i]))
ef416fc2 738 {
739 encoding = (cups_encoding_t)i;
740 break;
741 }
8ca02f3c 742
743 if (encoding == CUPS_AUTO_ENCODING)
744 {
745 /*
746 * Map alternate names for various character sets...
747 */
748
88f9aafc
MS
749 if (!_cups_strcasecmp(charset, "iso-2022-jp") ||
750 !_cups_strcasecmp(charset, "sjis"))
8ca02f3c 751 encoding = CUPS_WINDOWS_932;
88f9aafc 752 else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
8ca02f3c 753 encoding = CUPS_WINDOWS_936;
88f9aafc 754 else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
8ca02f3c 755 encoding = CUPS_WINDOWS_949;
88f9aafc 756 else if (!_cups_strcasecmp(charset, "big5"))
8ca02f3c 757 encoding = CUPS_WINDOWS_950;
758 }
ef416fc2 759 }
760
f11a948a 761 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding,
ef416fc2 762 encoding == CUPS_AUTO_ENCODING ? "auto" :
763 lang_encodings[encoding]));
764
765 /*
766 * See if we already have this language/country loaded...
767 */
768
bd7854cb 769 if (country[0])
bd7854cb 770 snprintf(real, sizeof(real), "%s_%s", langname, country);
bd7854cb 771 else
5a9febac 772 strlcpy(real, langname, sizeof(real));
d6ae789d 773
6d2f911b 774 _cupsMutexLock(&lang_mutex);
d6ae789d 775
c0e1af83 776 if ((lang = cups_cache_lookup(real, encoding)) != NULL)
d6ae789d 777 {
6d2f911b 778 _cupsMutexUnlock(&lang_mutex);
d6ae789d 779
f11a948a 780 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real));
c9fc04c6 781
d6ae789d 782 return (lang);
783 }
ef416fc2 784
ef416fc2 785 /*
786 * See if there is a free language available; if so, use that
787 * record...
788 */
789
d6ae789d 790 for (lang = lang_cache; lang != NULL; lang = lang->next)
ef416fc2 791 if (lang->used == 0)
792 break;
793
794 if (lang == NULL)
795 {
796 /*
797 * Allocate memory for the language and add it to the cache.
798 */
799
800 if ((lang = calloc(sizeof(cups_lang_t), 1)) == NULL)
d6ae789d 801 {
6d2f911b 802 _cupsMutexUnlock(&lang_mutex);
d6ae789d 803
ef416fc2 804 return (NULL);
d6ae789d 805 }
ef416fc2 806
d6ae789d 807 lang->next = lang_cache;
808 lang_cache = lang;
ef416fc2 809 }
bd7854cb 810 else
811 {
812 /*
813 * Free all old strings as needed...
814 */
ef416fc2 815
bd7854cb 816 _cupsMessageFree(lang->strings);
9c80ffa2 817 lang->strings = NULL;
bd7854cb 818 }
ef416fc2 819
820 /*
821 * Then assign the language and encoding fields...
822 */
823
824 lang->used ++;
825 strlcpy(lang->language, real, sizeof(lang->language));
826
827 if (encoding != CUPS_AUTO_ENCODING)
828 lang->encoding = encoding;
829 else
830 lang->encoding = CUPS_UTF8;
831
ef416fc2 832 /*
833 * Return...
834 */
835
6d2f911b 836 _cupsMutexUnlock(&lang_mutex);
d6ae789d 837
ef416fc2 838 return (lang);
839}
840
841
842/*
843 * '_cupsLangString()' - Get a message string.
844 *
845 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
846 * convert the string to the language encoding.
847 */
848
849const char * /* O - Localized message */
850_cupsLangString(cups_lang_t *lang, /* I - Language */
851 const char *message) /* I - Message */
852{
6d2f911b
MS
853 const char *s; /* Localized message */
854
a9357c9d
MS
855
856 DEBUG_printf(("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang, message));
857
ef416fc2 858 /*
859 * Range check input...
860 */
861
37e7e6e0 862 if (!lang || !message || !*message)
ef416fc2 863 return (message);
864
6d2f911b 865 _cupsMutexLock(&lang_mutex);
d6ae789d 866
9c80ffa2
MS
867 /*
868 * Load the message catalog if needed...
869 */
870
871 if (!lang->strings)
872 cups_message_load(lang);
873
6d2f911b 874 s = _cupsMessageLookup(lang->strings, message);
d6ae789d 875
6d2f911b 876 _cupsMutexUnlock(&lang_mutex);
d6ae789d 877
6d2f911b 878 return (s);
ef416fc2 879}
880
881
882/*
883 * '_cupsMessageFree()' - Free a messages array.
884 */
885
886void
887_cupsMessageFree(cups_array_t *a) /* I - Message array */
888{
0837b7e8
MS
889#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
890 /*
891 * Release the cups.strings dictionary as needed...
892 */
ef416fc2 893
0837b7e8
MS
894 if (cupsArrayUserData(a))
895 CFRelease((CFDictionaryRef)cupsArrayUserData(a));
896#endif /* __APPLE__ && CUPS_BUNDLEDIR */
ef416fc2 897
898 /*
899 * Free the array...
900 */
901
902 cupsArrayDelete(a);
903}
904
905
906/*
89550f3f 907 * '_cupsMessageLoad()' - Load a .po or .strings file into a messages array.
ef416fc2 908 */
909
910cups_array_t * /* O - New message array */
8b116e60 911_cupsMessageLoad(const char *filename, /* I - Message catalog to load */
89550f3f 912 int flags) /* I - Load flags */
ef416fc2 913{
914 cups_file_t *fp; /* Message file */
915 cups_array_t *a; /* Message array */
916 _cups_message_t *m; /* Current message */
917 char s[4096], /* String buffer */
918 *ptr, /* Pointer into buffer */
919 *temp; /* New string */
7e86f2f6
MS
920 size_t length, /* Length of combined strings */
921 ptrlen; /* Length of string */
ef416fc2 922
923
e07d4801 924 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename));
bd7854cb 925
ef416fc2 926 /*
927 * Create an array to hold the messages...
928 */
929
a29fd7dd 930 if ((a = _cupsMessageNew(NULL)) == NULL)
c9fc04c6 931 {
e07d4801 932 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
ef416fc2 933 return (NULL);
c9fc04c6 934 }
ef416fc2 935
936 /*
937 * Open the message catalog file...
938 */
939
940 if ((fp = cupsFileOpen(filename, "r")) == NULL)
c9fc04c6 941 {
e07d4801 942 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
c9fc04c6 943 strerror(errno)));
ef416fc2 944 return (a);
c9fc04c6 945 }
ef416fc2 946
89550f3f
MS
947 if (flags & _CUPS_MESSAGE_STRINGS)
948 {
949 while (cups_read_strings(fp, flags, a));
950 }
951 else
ef416fc2 952 {
953 /*
89550f3f
MS
954 * Read messages from the catalog file until EOF...
955 *
956 * The format is the GNU gettext .po format, which is fairly simple:
957 *
958 * msgid "some text"
959 * msgstr "localized text"
960 *
961 * The ID and localized text can span multiple lines using the form:
962 *
963 * msgid ""
964 * "some long text"
965 * msgstr ""
966 * "localized text spanning "
967 * "multiple lines"
ef416fc2 968 */
969
89550f3f 970 m = NULL;
c8fef167 971
89550f3f
MS
972 while (cupsFileGets(fp, s, sizeof(s)) != NULL)
973 {
974 /*
975 * Skip blank and comment lines...
976 */
ef416fc2 977
89550f3f
MS
978 if (s[0] == '#' || !s[0])
979 continue;
ef416fc2 980
89550f3f
MS
981 /*
982 * Strip the trailing quote...
983 */
ef416fc2 984
89550f3f
MS
985 if ((ptr = strrchr(s, '\"')) == NULL)
986 continue;
ef416fc2 987
89550f3f 988 *ptr = '\0';
ef416fc2 989
bd7854cb 990 /*
89550f3f 991 * Find start of value...
bd7854cb 992 */
993
89550f3f
MS
994 if ((ptr = strchr(s, '\"')) == NULL)
995 continue;
996
997 ptr ++;
bd7854cb 998
999 /*
89550f3f 1000 * Unquote the text...
bd7854cb 1001 */
1002
89550f3f
MS
1003 if (flags & _CUPS_MESSAGE_UNQUOTE)
1004 cups_unquote(ptr, ptr);
ef416fc2 1005
bd7854cb 1006 /*
89550f3f 1007 * Create or add to a message...
bd7854cb 1008 */
ef416fc2 1009
89550f3f 1010 if (!strncmp(s, "msgid", 5))
bd7854cb 1011 {
89550f3f
MS
1012 /*
1013 * Add previous message as needed...
1014 */
82cc1f9a 1015
89550f3f
MS
1016 if (m)
1017 {
1018 if (m->str && m->str[0])
1019 {
1020 cupsArrayAdd(a, m);
1021 }
1022 else
1023 {
1024 /*
1025 * Translation is empty, don't add it... (STR #4033)
1026 */
1027
1028 free(m->msg);
1029 if (m->str)
1030 free(m->str);
1031 free(m);
1032 }
1033 }
ef416fc2 1034
1035 /*
89550f3f 1036 * Create a new message with the given msgid string...
ef416fc2 1037 */
1038
89550f3f
MS
1039 if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
1040 break;
bd7854cb 1041
89550f3f
MS
1042 if ((m->msg = strdup(ptr)) == NULL)
1043 {
1044 free(m);
1045 m = NULL;
1046 break;
1047 }
ef416fc2 1048 }
89550f3f 1049 else if (s[0] == '\"' && m)
ef416fc2 1050 {
1051 /*
89550f3f 1052 * Append to current string...
ef416fc2 1053 */
1054
89550f3f
MS
1055 length = strlen(m->str ? m->str : m->msg);
1056 ptrlen = strlen(ptr);
bd7854cb 1057
89550f3f
MS
1058 if ((temp = realloc(m->str ? m->str : m->msg, length + ptrlen + 1)) == NULL)
1059 {
1060 if (m->str)
1061 free(m->str);
1062 free(m->msg);
1063 free(m);
1064 m = NULL;
1065 break;
1066 }
bd7854cb 1067
89550f3f
MS
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->msg = temp;
1089
1090 memcpy(m->msg + length, ptr, ptrlen + 1);
1091 }
1092 }
1093 else if (!strncmp(s, "msgstr", 6) && m)
91c84a35 1094 {
89550f3f
MS
1095 /*
1096 * Set the string...
1097 */
82cc1f9a 1098
89550f3f
MS
1099 if ((m->str = strdup(ptr)) == NULL)
1100 {
1101 free(m->msg);
1102 free(m);
1103 m = NULL;
1104 break;
1105 }
91c84a35 1106 }
bd7854cb 1107 }
ef416fc2 1108
89550f3f
MS
1109 /*
1110 * Add the last message string to the array as needed...
1111 */
bd7854cb 1112
89550f3f 1113 if (m)
82cc1f9a 1114 {
89550f3f
MS
1115 if (m->str && m->str[0])
1116 {
1117 cupsArrayAdd(a, m);
1118 }
1119 else
1120 {
1121 /*
1122 * Translation is empty, don't add it... (STR #4033)
1123 */
82cc1f9a 1124
89550f3f
MS
1125 free(m->msg);
1126 if (m->str)
1127 free(m->str);
1128 free(m);
1129 }
82cc1f9a
MS
1130 }
1131 }
bd7854cb 1132
ef416fc2 1133 /*
1134 * Close the message catalog file and return the new array...
1135 */
1136
1137 cupsFileClose(fp);
1138
89550f3f 1139 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...", cupsArrayCount(a)));
c9fc04c6 1140
ef416fc2 1141 return (a);
1142}
1143
1144
1145/*
1146 * '_cupsMessageLookup()' - Lookup a message string.
1147 */
1148
1149const char * /* O - Localized message */
1150_cupsMessageLookup(cups_array_t *a, /* I - Message array */
1151 const char *m) /* I - Message */
1152{
1153 _cups_message_t key, /* Search key */
1154 *match; /* Matching message */
1155
1156
a9357c9d
MS
1157 DEBUG_printf(("_cupsMessageLookup(a=%p, m=\"%s\")", (void *)a, m));
1158
ef416fc2 1159 /*
1160 * Lookup the message string; if it doesn't exist in the catalog,
1161 * then return the message that was passed to us...
1162 */
1163
89550f3f
MS
1164 key.msg = (char *)m;
1165 match = (_cups_message_t *)cupsArrayFind(a, &key);
ef416fc2 1166
0837b7e8
MS
1167#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1168 if (!match && cupsArrayUserData(a))
1169 {
1170 /*
1171 * Try looking the string up in the cups.strings dictionary...
1172 */
1173
1174 CFDictionaryRef dict; /* cups.strings dictionary */
1175 CFStringRef cfm, /* Message as a CF string */
1176 cfstr; /* Localized text as a CF string */
1177
89550f3f
MS
1178 dict = (CFDictionaryRef)cupsArrayUserData(a);
1179 cfm = CFStringCreateWithCString(kCFAllocatorDefault, m, kCFStringEncodingUTF8);
1180 match = calloc(1, sizeof(_cups_message_t));
1181 match->msg = strdup(m);
1182 cfstr = cfm ? CFDictionaryGetValue(dict, cfm) : NULL;
0837b7e8
MS
1183
1184 if (cfstr)
1185 {
1186 char buffer[1024]; /* Message buffer */
1187
1188 CFStringGetCString(cfstr, buffer, sizeof(buffer), kCFStringEncodingUTF8);
1189 match->str = strdup(buffer);
c8fef167 1190
89550f3f 1191 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...", m, buffer));
0837b7e8
MS
1192 }
1193 else
c8fef167 1194 {
0837b7e8
MS
1195 match->str = strdup(m);
1196
c8fef167
MS
1197 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m));
1198 }
1199
0837b7e8 1200 cupsArrayAdd(a, match);
e60ec91f
MS
1201
1202 if (cfm)
1203 CFRelease(cfm);
0837b7e8
MS
1204 }
1205#endif /* __APPLE__ && CUPS_BUNDLEDIR */
1206
ef416fc2 1207 if (match && match->str)
1208 return (match->str);
1209 else
1210 return (m);
1211}
1212
1213
a29fd7dd
MS
1214/*
1215 * '_cupsMessageNew()' - Make a new message catalog array.
1216 */
1217
1218cups_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
ef416fc2 1228#ifdef __APPLE__
ef416fc2 1229/*
1230 * 'appleLangDefault()' - Get the default locale string.
1231 */
1232
1233static const char * /* O - Locale string */
1234appleLangDefault(void)
1235{
db1f069b
MS
1236 CFBundleRef bundle; /* Main bundle (if any) */
1237 CFArrayRef bundleList; /* List of localizations in bundle */
b2656447 1238 CFPropertyListRef localizationList = NULL;
ef416fc2 1239 /* List of localization data */
1240 CFStringRef languageName; /* Current name */
26d47ec6 1241 char *lang; /* LANG environment variable */
ef416fc2 1242 _cups_globals_t *cg = _cupsGlobals();
1243 /* Pointer to library globals */
1244
1245
b0f6947b
MS
1246 DEBUG_puts("2appleLangDefault()");
1247
ef416fc2 1248 /*
1249 * Only do the lookup and translation the first time.
1250 */
1251
1252 if (!cg->language[0])
1253 {
b0f6947b 1254 if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL)
db1f069b 1255 {
94436c5a 1256 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang));
26d47ec6 1257 strlcpy(cg->language, lang, sizeof(cg->language));
db1f069b
MS
1258 return (cg->language);
1259 }
1260 else if ((bundle = CFBundleGetMainBundle()) != NULL &&
1261 (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
ef416fc2 1262 {
b2656447
MS
1263 CFURLRef resources = CFBundleCopyResourcesDirectoryURL(bundle);
1264
94436c5a
MS
1265 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
1266
b2656447
MS
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
36474350 1278 CFStringGetCString(cfpath, path, sizeof(path), kCFStringEncodingUTF8);
b2656447
MS
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.");
26d47ec6 1294
db1f069b
MS
1295 CFRelease(bundleList);
1296 }
b2656447
MS
1297
1298 if (!localizationList)
94436c5a
MS
1299 {
1300 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
1301
db1f069b
MS
1302 localizationList =
1303 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1304 kCFPreferencesCurrentApplication);
94436c5a 1305 }
db1f069b
MS
1306
1307 if (localizationList)
1308 {
94436c5a
MS
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
db1f069b
MS
1317 if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
1318 CFArrayGetCount(localizationList) > 0)
ef416fc2 1319 {
db1f069b
MS
1320 languageName = CFArrayGetValueAtIndex(localizationList, 0);
1321
1322 if (languageName &&
1323 CFGetTypeID(languageName) == CFStringGetTypeID())
26d47ec6 1324 {
454708a4 1325 if (_cupsAppleLocale(languageName, cg->language, sizeof(cg->language)))
94436c5a 1326 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
db1f069b 1327 cg->language));
94436c5a 1328 else
454708a4 1329 DEBUG_puts("3appleLangDefault: Unable to get locale.");
26d47ec6 1330 }
26d47ec6 1331 }
db1f069b
MS
1332
1333 CFRelease(localizationList);
ef416fc2 1334 }
c8fef167 1335
ef416fc2 1336 /*
1337 * If we didn't find the language, default to en_US...
1338 */
1339
1340 if (!cg->language[0])
94436c5a
MS
1341 {
1342 DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
ef416fc2 1343 strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
94436c5a 1344 }
ef416fc2 1345 }
36474350
MS
1346 else
1347 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg->language));
ef416fc2 1348
1349 /*
1350 * Return the cached locale...
1351 */
1352
1353 return (cg->language);
1354}
0837b7e8
MS
1355
1356
1357# ifdef CUPS_BUNDLEDIR
1358/*
1359 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1360 */
1361
1362static cups_array_t * /* O - Message catalog */
1363appleMessageLoad(const char *locale) /* I - Locale ID */
1364{
1365 char filename[1024], /* Path to cups.strings file */
36ac4374 1366 applelang[256], /* Apple language ID */
fdc80a91 1367 baselang[4]; /* Base language */
0837b7e8 1368 CFURLRef url; /* URL to cups.strings file */
c8fef167
MS
1369 CFReadStreamRef stream = NULL; /* File stream */
1370 CFPropertyListRef plist = NULL; /* Localization file */
1371#ifdef DEBUG
a9357c9d
MS
1372 const char *cups_strings = getenv("CUPS_STRINGS");
1373 /* Test strings file */
c8fef167
MS
1374 CFErrorRef error = NULL; /* Error when opening file */
1375#endif /* DEBUG */
1376
1377
1378 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale));
0837b7e8
MS
1379
1380 /*
1381 * Load the cups.strings file...
1382 */
1383
a9357c9d
MS
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
0837b7e8
MS
1393 snprintf(filename, sizeof(filename),
1394 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings",
1395 _cupsAppleLanguage(locale, applelang, sizeof(applelang)));
88da9d73
MS
1396
1397 if (access(filename, 0))
1398 {
1399 /*
1400 * <rdar://problem/22086642>
1401 *
1402 * Try with original locale string...
1403 */
1404
a9357c9d 1405 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
88da9d73
MS
1406 snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
1407 }
1408
b052deed
MS
1409 if (access(filename, 0))
1410 {
1411 /*
1412 * <rdar://problem/25292403>
1413 *
1414 * Try with just the language code...
1415 */
1416
a9357c9d
MS
1417 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
1418
b052deed 1419 strlcpy(baselang, locale, sizeof(baselang));
fdc80a91
MS
1420 if (baselang[3] == '-' || baselang[3] == '_')
1421 baselang[3] = '\0';
1422
b052deed
MS
1423 snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", baselang);
1424 }
1425
c8fef167
MS
1426 if (access(filename, 0))
1427 {
1428 /*
1429 * Try alternate lproj directory names...
1430 */
1431
a9357c9d
MS
1432 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
1433
c8fef167
MS
1434 if (!strncmp(locale, "en", 2))
1435 locale = "English";
b052deed
MS
1436 else if (!strncmp(locale, "nb", 2))
1437 locale = "no";
1438 else if (!strncmp(locale, "nl", 2))
c8fef167
MS
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";
fdc80a91 1450 else if (!strcmp(locale, "zh_HK") || !strncasecmp(locale, "zh-Hant", 7) || !strncasecmp(locale, "zh_Hant", 7))
bd3e2e22
MS
1451 {
1452 /*
1453 * <rdar://problem/22130168>
14fdcd7a 1454 * <rdar://problem/27245567>
bd3e2e22
MS
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 }
36ac4374
MS
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));
997db404
MS
1471 if (baselang[2] == '-' || baselang[2] == '_')
1472 baselang[2] = '\0';
fdc80a91 1473
36ac4374
MS
1474 locale = baselang;
1475 }
c8fef167
MS
1476
1477 snprintf(filename, sizeof(filename),
1478 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
c8fef167
MS
1479 }
1480
a9357c9d
MS
1481 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename));
1482
c8fef167
MS
1483 url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
1484 (UInt8 *)filename,
7e86f2f6 1485 (CFIndex)strlen(filename), false);
c8fef167
MS
1486 if (url)
1487 {
1488 stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
1489 if (stream)
1490 {
82f97232
MS
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
c8fef167
MS
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
82f97232 1513 CFRelease(msg);
c8fef167
MS
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 }
0837b7e8 1534
c8fef167
MS
1535 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url, stream,
1536 plist));
0837b7e8
MS
1537
1538 /*
1539 * Create and return an empty array to act as a cache for messages, passing the
c8fef167 1540 * plist as the user data.
0837b7e8
MS
1541 */
1542
a29fd7dd 1543 return (_cupsMessageNew((void *)plist));
0837b7e8
MS
1544}
1545# endif /* CUPS_BUNDLEDIR */
ef416fc2 1546#endif /* __APPLE__ */
1547
1548
1549/*
1550 * 'cups_cache_lookup()' - Lookup a language in the cache...
1551 */
1552
1553static cups_lang_t * /* O - Language data or NULL */
0837b7e8
MS
1554cups_cache_lookup(
1555 const char *name, /* I - Name of locale */
1556 cups_encoding_t encoding) /* I - Encoding of locale */
ef416fc2 1557{
1558 cups_lang_t *lang; /* Current language */
1559
1560
e07d4801 1561 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name,
ef416fc2 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
d6ae789d 1569 for (lang = lang_cache; lang != NULL; lang = lang->next)
ef416fc2 1570 {
e07d4801 1571 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
807315e6 1572 "encoding=%d(%s)", (void *)lang, lang->language, lang->encoding,
ef416fc2 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
e07d4801 1580 DEBUG_puts("8cups_cache_lookup: returning match!");
ef416fc2 1581
1582 return (lang);
1583 }
1584 }
1585
e07d4801 1586 DEBUG_puts("8cups_cache_lookup: returning NULL!");
ef416fc2 1587
1588 return (NULL);
1589}
1590
1591
1592/*
1593 * 'cups_message_compare()' - Compare two messages.
1594 */
1595
1596static int /* O - Result of comparison */
1597cups_message_compare(
1598 _cups_message_t *m1, /* I - First message */
1599 _cups_message_t *m2) /* I - Second message */
1600{
89550f3f 1601 return (strcmp(m1->msg, m2->msg));
ef416fc2 1602}
1603
1604
0837b7e8
MS
1605/*
1606 * 'cups_message_free()' - Free a message.
1607 */
1608
1609static void
1610cups_message_free(_cups_message_t *m) /* I - Message */
1611{
89550f3f
MS
1612 if (m->msg)
1613 free(m->msg);
0837b7e8
MS
1614
1615 if (m->str)
1616 free(m->str);
1617
1618 free(m);
1619}
1620
1621
9c80ffa2
MS
1622/*
1623 * 'cups_message_load()' - Load the message catalog for a language.
1624 */
1625
1626static void
1627cups_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
89550f3f 1667 lang->strings = _cupsMessageLoad(filename, _CUPS_MESSAGE_UNQUOTE);
9c80ffa2
MS
1668#endif /* __APPLE__ && CUPS_BUNDLEDIR */
1669}
1670
1671
89550f3f
MS
1672/*
1673 * 'cups_read_strings()' - Read a pair of strings from a .strings file.
1674 */
1675
1676static int /* O - 1 on success, 0 on failure */
1677cups_read_strings(cups_file_t *fp, /* I - .strings file */
1678 int flags, /* I - CUPS_MESSAGE_xxx flags */
1679 cups_array_t *a) /* I - Message catalog array */
1680{
1681 char buffer[8192], /* Line buffer */
1682 *bufptr, /* Pointer into buffer */
1683 *msg, /* Pointer to start of message */
1684 *str; /* Pointer to start of translation string */
1685 _cups_message_t *m; /* New message */
1686
1687
1688 while (cupsFileGets(fp, buffer, sizeof(buffer)))
1689 {
1690 /*
1691 * Skip any line (comments, blanks, etc.) that isn't:
1692 *
1693 * "message" = "translation";
1694 */
1695
1696 for (bufptr = buffer; *bufptr && isspace(*bufptr & 255); bufptr ++);
1697
1698 if (*bufptr != '\"')
1699 continue;
1700
1701 /*
1702 * Find the end of the message...
1703 */
1704
1705 bufptr ++;
1706 for (msg = bufptr; *bufptr && *bufptr != '\"'; bufptr ++)
1707 if (*bufptr == '\\' && bufptr[1])
1708 bufptr ++;
1709
1710 if (!*bufptr)
1711 continue;
1712
1713 *bufptr++ = '\0';
1714
1715 if (flags & _CUPS_MESSAGE_UNQUOTE)
1716 cups_unquote(msg, msg);
1717
1718 /*
1719 * Find the start of the translation...
1720 */
1721
1722 while (*bufptr && isspace(*bufptr & 255))
1723 bufptr ++;
1724
1725 if (*bufptr != '=')
1726 continue;
1727
1728 bufptr ++;
1729 while (*bufptr && isspace(*bufptr & 255))
1730 bufptr ++;
1731
1732 if (*bufptr != '\"')
1733 continue;
1734
1735 /*
1736 * Find the end of the translation...
1737 */
1738
1739 bufptr ++;
1740 for (str = bufptr; *bufptr && *bufptr != '\"'; bufptr ++)
1741 if (*bufptr == '\\' && bufptr[1])
1742 bufptr ++;
1743
1744 if (!*bufptr)
1745 continue;
1746
1747 *bufptr++ = '\0';
1748
1749 if (flags & _CUPS_MESSAGE_UNQUOTE)
1750 cups_unquote(str, str);
1751
1752 /*
1753 * If we get this far we have a valid pair of strings, add them...
1754 */
1755
1756 if ((m = malloc(sizeof(_cups_message_t))) == NULL)
1757 break;
1758
1759 m->msg = strdup(msg);
1760 m->str = strdup(str);
1761
1762 if (m->msg && m->str)
1763 {
1764 cupsArrayAdd(a, m);
1765 }
1766 else
1767 {
1768 if (m->msg)
1769 free(m->msg);
1770
1771 if (m->str)
1772 free(m->str);
1773
1774 free(m);
1775 break;
1776 }
1777
1778 return (1);
1779 }
1780
1781 /*
1782 * No more strings...
1783 */
1784
1785 return (0);
1786}
1787
1788
ef416fc2 1789/*
1790 * 'cups_unquote()' - Unquote characters in strings...
1791 */
1792
1793static void
1794cups_unquote(char *d, /* O - Unquoted string */
1795 const char *s) /* I - Original string */
1796{
1797 while (*s)
1798 {
1799 if (*s == '\\')
1800 {
1801 s ++;
1802 if (isdigit(*s))
1803 {
1804 *d = 0;
1805
1806 while (isdigit(*s))
1807 {
1808 *d = *d * 8 + *s - '0';
1809 s ++;
1810 }
d09495fa 1811
1812 d ++;
ef416fc2 1813 }
1814 else
1815 {
1816 if (*s == 'n')
1817 *d ++ = '\n';
1818 else if (*s == 'r')
1819 *d ++ = '\r';
1820 else if (*s == 't')
1821 *d ++ = '\t';
1822 else
1823 *d++ = *s;
1824
1825 s ++;
1826 }
1827 }
1828 else
1829 *d++ = *s++;
1830 }
1831
1832 *d = '\0';
1833}