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