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