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