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