]> git.ipfire.org Git - thirdparty/cups.git/blob - cups/language.c
78d7d1573d4e7302400acb7c6b0eaf2d2bd0c3e0
[thirdparty/cups.git] / cups / language.c
1 /*
2 * I18N/language support for CUPS.
3 *
4 * Copyright 2007-2017 by Apple Inc.
5 * Copyright 1997-2007 by Easy Software Products.
6 *
7 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
8 */
9
10 /*
11 * Include necessary headers...
12 */
13
14 #include "cups-private.h"
15 #ifdef HAVE_LANGINFO_H
16 # include <langinfo.h>
17 #endif /* HAVE_LANGINFO_H */
18 #ifdef WIN32
19 # include <io.h>
20 #else
21 # include <unistd.h>
22 #endif /* WIN32 */
23 #ifdef HAVE_COREFOUNDATION_H
24 # include <CoreFoundation/CoreFoundation.h>
25 #endif /* HAVE_COREFOUNDATION_H */
26
27
28 /*
29 * Local globals...
30 */
31
32 static _cups_mutex_t lang_mutex = _CUPS_MUTEX_INITIALIZER;
33 /* Mutex to control access to cache */
34 static cups_lang_t *lang_cache = NULL;
35 /* Language string cache */
36 static 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",
45 "iso-8859-15", "cp874",
46 "cp1250", "cp1251",
47 "cp1252", "cp1253",
48 "cp1254", "cp1255",
49 "cp1256", "cp1257",
50 "cp1258", "koi8-r",
51 "koi8-u", "iso-8859-11",
52 "iso-8859-16", "mac",
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",
70 "cp932", "cp936",
71 "cp949", "cp950",
72 "cp1361", "unknown",
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",
103 "euc-kr", "euc-tw",
104 "shift_jisx0213"
105 };
106
107 #ifdef __APPLE__
108 typedef struct
109 {
110 const char * const language; /* Language ID */
111 const char * const locale; /* Locale ID */
112 } _apple_language_locale_t;
113
114 static const _apple_language_locale_t apple_language_locale[] =
115 { /* Language to locale ID LUT */
116 { "en", "en_US" },
117 { "nb", "no" },
118 { "nb_NO", "no" },
119 { "zh-Hans", "zh_CN" },
120 { "zh_HANS", "zh_CN" },
121 { "zh-Hant", "zh_TW" },
122 { "zh_HANT", "zh_TW" },
123 { "zh-Hant_CN", "zh_TW" }
124 };
125 #endif /* __APPLE__ */
126
127
128 /*
129 * Local functions...
130 */
131
132
133 #ifdef __APPLE__
134 static const char *appleLangDefault(void);
135 # ifdef CUPS_BUNDLEDIR
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 */
143 static cups_array_t *appleMessageLoad(const char *locale) CF_RETURNS_RETAINED;
144 # endif /* CUPS_BUNDLEDIR */
145 #endif /* __APPLE__ */
146 static cups_lang_t *cups_cache_lookup(const char *name, cups_encoding_t encoding);
147 static int cups_message_compare(_cups_message_t *m1, _cups_message_t *m2);
148 static void cups_message_free(_cups_message_t *m);
149 static void cups_message_load(cups_lang_t *lang);
150 static void cups_message_puts(cups_file_t *fp, const char *s);
151 static int cups_read_strings(cups_file_t *fp, int flags, cups_array_t *a);
152 static void cups_unquote(char *d, const char *s);
153
154
155 #ifdef __APPLE__
156 /*
157 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
158 * locale ID.
159 */
160
161 const 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] = '_';
200 language[3] = (char)toupper(language[3] & 255);
201 language[4] = (char)toupper(language[4] & 255);
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 {
226 CFStringGetCString(langid, language, (CFIndex)langsize, kCFStringEncodingASCII);
227 CFRelease(langid);
228 }
229
230 CFRelease(localeid);
231 }
232
233 /*
234 * Return what we got...
235 */
236
237 return (language);
238 }
239
240
241 /*
242 * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID.
243 */
244
245 const 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 */
252 #ifdef DEBUG
253 char temp[1024]; /* Temporary string */
254
255
256 if (!CFStringGetCString(languageName, temp, (CFIndex)sizeof(temp), kCFStringEncodingASCII))
257 temp[0] = '\0';
258
259 DEBUG_printf(("_cupsAppleLocale(languageName=%p(%s), locale=%p, localsize=%d)", (void *)languageName, temp, (void *)locale, (int)localesize));
260 #endif /* DEBUG */
261
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
273 DEBUG_printf(("_cupsAppleLocale: locale=\"%s\"", locale));
274
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 {
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] == '-')))
290 {
291 DEBUG_printf(("_cupsAppleLocale: Updating locale to \"%s\".", apple_language_locale[i].locale));
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)
308 {
309 DEBUG_puts("_cupsAppleLocale: Returning NULL.");
310 return (NULL);
311 }
312
313 /*
314 * Convert language subtag into region subtag...
315 */
316
317 if (locale[2] == '-')
318 locale[2] = '_';
319 else if (locale[3] == '-')
320 locale[3] = '_';
321
322 if (!strchr(locale, '.'))
323 strlcat(locale, ".UTF-8", localesize);
324
325 DEBUG_printf(("_cupsAppleLocale: Returning \"%s\".", locale));
326
327 return (locale);
328 }
329 #endif /* __APPLE__ */
330
331
332 /*
333 * '_cupsEncodingName()' - Return the character encoding name string
334 * for the given encoding enumeration.
335 */
336
337 const char * /* O - Character encoding */
338 _cupsEncodingName(
339 cups_encoding_t encoding) /* I - Encoding value */
340 {
341 if (encoding < CUPS_US_ASCII ||
342 encoding >= (cups_encoding_t)(sizeof(lang_encodings) / sizeof(lang_encodings[0])))
343 {
344 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
345 encoding, lang_encodings[0]));
346 return (lang_encodings[0]);
347 }
348 else
349 {
350 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
351 encoding, lang_encodings[encoding]));
352 return (lang_encodings[encoding]);
353 }
354 }
355
356
357 /*
358 * 'cupsLangDefault()' - Return the default language.
359 */
360
361 cups_lang_t * /* O - Language data */
362 cupsLangDefault(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
373 const char * /* O - Character encoding */
374 cupsLangEncoding(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
387 void
388 cupsLangFlush(void)
389 {
390 cups_lang_t *lang, /* Current language */
391 *next; /* Next language */
392
393
394 /*
395 * Free all languages in the cache...
396 */
397
398 _cupsMutexLock(&lang_mutex);
399
400 for (lang = lang_cache; lang != NULL; lang = next)
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
416 lang_cache = NULL;
417
418 _cupsMutexUnlock(&lang_mutex);
419 }
420
421
422 /*
423 * 'cupsLangFree()' - Free language data.
424 *
425 * This does not actually free anything; use @link cupsLangFlush@ for that.
426 */
427
428 void
429 cupsLangFree(cups_lang_t *lang) /* I - Language to free */
430 {
431 _cupsMutexLock(&lang_mutex);
432
433 if (lang != NULL && lang->used > 0)
434 lang->used --;
435
436 _cupsMutexUnlock(&lang_mutex);
437 }
438
439
440 /*
441 * 'cupsLangGet()' - Get a language.
442 */
443
444 cups_lang_t * /* O - Language data */
445 cupsLangGet(const char *language) /* I - Language or locale */
446 {
447 int i; /* Looping var */
448 #ifndef __APPLE__
449 char locale[255]; /* Copy of locale name */
450 #endif /* !__APPLE__ */
451 char langname[16], /* Requested language name */
452 country[16], /* Country code */
453 charset[16], /* Character set */
454 *csptr, /* Pointer to CODESET string */
455 *ptr, /* Pointer into language/charset */
456 real[48]; /* Real language name */
457 cups_encoding_t encoding; /* Encoding to use */
458 cups_lang_t *lang; /* Current language... */
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",
468 "ISO885916", "MACROMAN", "", "",
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
497 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
498 "SHIFT_JISX0213"
499 };
500
501
502 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language));
503
504 #ifdef __APPLE__
505 /*
506 * Set the character set to UTF-8...
507 */
508
509 strlcpy(charset, "UTF8", sizeof(charset));
510
511 /*
512 * Apple's setlocale doesn't give us the user's localization
513 * preference so we have to look it up this way...
514 */
515
516 if (!language)
517 {
518 if (!getenv("SOFTWARE") || (language = getenv("LANG")) == NULL)
519 language = appleLangDefault();
520
521 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language));
522 }
523
524 #else
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)
538 {
539 /*
540 * First see if the locale has been set; if it is still "C" or
541 * "POSIX", use the environment to get the default...
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
550 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr));
551
552 if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
553 {
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 ++)
570 if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr))
571 *ptr++ = *csptr;
572
573 *ptr = '\0';
574 }
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";
584 }
585
586 if (ptr)
587 {
588 strlcpy(locale, ptr, sizeof(locale));
589 language = locale;
590
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
598 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language));
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 /*
611 * Switch to the POSIX ("C") locale...
612 */
613
614 language = "C";
615 }
616
617 #ifdef CODESET
618 /*
619 * On systems that support the nl_langinfo(CODESET) call, use
620 * this value as the character set...
621 */
622
623 if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
624 {
625 /*
626 * Copy all of the letters and numbers in the CODESET string...
627 */
628
629 for (ptr = charset; *csptr; csptr ++)
630 if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1))
631 *ptr++ = *csptr;
632
633 *ptr = '\0';
634
635 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
636 "nl_langinfo(CODESET)...", charset));
637 }
638 #endif /* CODESET */
639
640 /*
641 * If we don't have a character set by now, default to UTF-8...
642 */
643
644 if (!charset[0])
645 strlcpy(charset, "UTF8", sizeof(charset));
646
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"))
660 strlcpy(langname, "C", sizeof(langname));
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))
671 *ptr++ = (char)tolower(*language & 255);
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))
685 *ptr++ = (char)toupper(*language & 255);
686
687 *ptr = '\0';
688
689 /*
690 * Map Chinese region codes to legacy country codes.
691 */
692
693 if (!strcmp(language, "zh") && !strcmp(country, "HANS"))
694 strlcpy(country, "CN", sizeof(country));
695 if (!strcmp(language, "zh") && !strcmp(country, "HANT"))
696 strlcpy(country, "TW", sizeof(country));
697 }
698
699 if (*language == '.' && !charset[0])
700 {
701 /*
702 * Copy the encoding...
703 */
704
705 for (language ++, ptr = charset; *language; language ++)
706 if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1))
707 *ptr++ = (char)toupper(*language & 255);
708
709 *ptr = '\0';
710 }
711
712 /*
713 * Force a POSIX locale for an invalid language name...
714 */
715
716 if (strlen(langname) != 2 && strlen(langname) != 3)
717 {
718 strlcpy(langname, "C", sizeof(langname));
719 country[0] = '\0';
720 charset[0] = '\0';
721 }
722 }
723
724 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
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 {
735 for (i = 0;
736 i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
737 i ++)
738 if (!_cups_strcasecmp(charset, locale_encodings[i]))
739 {
740 encoding = (cups_encoding_t)i;
741 break;
742 }
743
744 if (encoding == CUPS_AUTO_ENCODING)
745 {
746 /*
747 * Map alternate names for various character sets...
748 */
749
750 if (!_cups_strcasecmp(charset, "iso-2022-jp") ||
751 !_cups_strcasecmp(charset, "sjis"))
752 encoding = CUPS_WINDOWS_932;
753 else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
754 encoding = CUPS_WINDOWS_936;
755 else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
756 encoding = CUPS_WINDOWS_949;
757 else if (!_cups_strcasecmp(charset, "big5"))
758 encoding = CUPS_WINDOWS_950;
759 }
760 }
761
762 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding,
763 encoding == CUPS_AUTO_ENCODING ? "auto" :
764 lang_encodings[encoding]));
765
766 /*
767 * See if we already have this language/country loaded...
768 */
769
770 if (country[0])
771 snprintf(real, sizeof(real), "%s_%s", langname, country);
772 else
773 strlcpy(real, langname, sizeof(real));
774
775 _cupsMutexLock(&lang_mutex);
776
777 if ((lang = cups_cache_lookup(real, encoding)) != NULL)
778 {
779 _cupsMutexUnlock(&lang_mutex);
780
781 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real));
782
783 return (lang);
784 }
785
786 /*
787 * See if there is a free language available; if so, use that
788 * record...
789 */
790
791 for (lang = lang_cache; lang != NULL; lang = lang->next)
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)
802 {
803 _cupsMutexUnlock(&lang_mutex);
804
805 return (NULL);
806 }
807
808 lang->next = lang_cache;
809 lang_cache = lang;
810 }
811 else
812 {
813 /*
814 * Free all old strings as needed...
815 */
816
817 _cupsMessageFree(lang->strings);
818 lang->strings = NULL;
819 }
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
833 /*
834 * Return...
835 */
836
837 _cupsMutexUnlock(&lang_mutex);
838
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
850 const char * /* O - Localized message */
851 _cupsLangString(cups_lang_t *lang, /* I - Language */
852 const char *message) /* I - Message */
853 {
854 const char *s; /* Localized message */
855
856
857 DEBUG_printf(("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang, message));
858
859 /*
860 * Range check input...
861 */
862
863 if (!lang || !message || !*message)
864 return (message);
865
866 _cupsMutexLock(&lang_mutex);
867
868 /*
869 * Load the message catalog if needed...
870 */
871
872 if (!lang->strings)
873 cups_message_load(lang);
874
875 s = _cupsMessageLookup(lang->strings, message);
876
877 _cupsMutexUnlock(&lang_mutex);
878
879 return (s);
880 }
881
882
883 /*
884 * '_cupsMessageFree()' - Free a messages array.
885 */
886
887 void
888 _cupsMessageFree(cups_array_t *a) /* I - Message array */
889 {
890 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
891 /*
892 * Release the cups.strings dictionary as needed...
893 */
894
895 if (cupsArrayUserData(a))
896 CFRelease((CFDictionaryRef)cupsArrayUserData(a));
897 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
898
899 /*
900 * Free the array...
901 */
902
903 cupsArrayDelete(a);
904 }
905
906
907 /*
908 * '_cupsMessageLoad()' - Load a .po or .strings file into a messages array.
909 */
910
911 cups_array_t * /* O - New message array */
912 _cupsMessageLoad(const char *filename, /* I - Message catalog to load */
913 int flags) /* I - Load flags */
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 */
921 size_t length, /* Length of combined strings */
922 ptrlen; /* Length of string */
923
924
925 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename));
926
927 /*
928 * Create an array to hold the messages...
929 */
930
931 if ((a = _cupsMessageNew(NULL)) == NULL)
932 {
933 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
934 return (NULL);
935 }
936
937 /*
938 * Open the message catalog file...
939 */
940
941 if ((fp = cupsFileOpen(filename, "r")) == NULL)
942 {
943 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
944 strerror(errno)));
945 return (a);
946 }
947
948 if (flags & _CUPS_MESSAGE_STRINGS)
949 {
950 while (cups_read_strings(fp, flags, a));
951 }
952 else
953 {
954 /*
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"
969 */
970
971 m = NULL;
972
973 while (cupsFileGets(fp, s, sizeof(s)) != NULL)
974 {
975 /*
976 * Skip blank and comment lines...
977 */
978
979 if (s[0] == '#' || !s[0])
980 continue;
981
982 /*
983 * Strip the trailing quote...
984 */
985
986 if ((ptr = strrchr(s, '\"')) == NULL)
987 continue;
988
989 *ptr = '\0';
990
991 /*
992 * Find start of value...
993 */
994
995 if ((ptr = strchr(s, '\"')) == NULL)
996 continue;
997
998 ptr ++;
999
1000 /*
1001 * Unquote the text...
1002 */
1003
1004 if (flags & _CUPS_MESSAGE_UNQUOTE)
1005 cups_unquote(ptr, ptr);
1006
1007 /*
1008 * Create or add to a message...
1009 */
1010
1011 if (!strncmp(s, "msgid", 5))
1012 {
1013 /*
1014 * Add previous message as needed...
1015 */
1016
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 }
1035
1036 /*
1037 * Create a new message with the given msgid string...
1038 */
1039
1040 if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
1041 break;
1042
1043 if ((m->msg = strdup(ptr)) == NULL)
1044 {
1045 free(m);
1046 m = NULL;
1047 break;
1048 }
1049 }
1050 else if (s[0] == '\"' && m)
1051 {
1052 /*
1053 * Append to current string...
1054 */
1055
1056 length = strlen(m->str ? m->str : m->msg);
1057 ptrlen = strlen(ptr);
1058
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 }
1068
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)
1095 {
1096 /*
1097 * Set the string...
1098 */
1099
1100 if ((m->str = strdup(ptr)) == NULL)
1101 {
1102 free(m->msg);
1103 free(m);
1104 m = NULL;
1105 break;
1106 }
1107 }
1108 }
1109
1110 /*
1111 * Add the last message string to the array as needed...
1112 */
1113
1114 if (m)
1115 {
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 */
1125
1126 free(m->msg);
1127 if (m->str)
1128 free(m->str);
1129 free(m);
1130 }
1131 }
1132 }
1133
1134 /*
1135 * Close the message catalog file and return the new array...
1136 */
1137
1138 cupsFileClose(fp);
1139
1140 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...", cupsArrayCount(a)));
1141
1142 return (a);
1143 }
1144
1145
1146 /*
1147 * '_cupsMessageLookup()' - Lookup a message string.
1148 */
1149
1150 const 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
1158 DEBUG_printf(("_cupsMessageLookup(a=%p, m=\"%s\")", (void *)a, m));
1159
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
1165 key.msg = (char *)m;
1166 match = (_cups_message_t *)cupsArrayFind(a, &key);
1167
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
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;
1184
1185 if (cfstr)
1186 {
1187 char buffer[1024]; /* Message buffer */
1188
1189 CFStringGetCString(cfstr, buffer, sizeof(buffer), kCFStringEncodingUTF8);
1190 match->str = strdup(buffer);
1191
1192 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...", m, buffer));
1193 }
1194 else
1195 {
1196 match->str = strdup(m);
1197
1198 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m));
1199 }
1200
1201 cupsArrayAdd(a, match);
1202
1203 if (cfm)
1204 CFRelease(cfm);
1205 }
1206 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1207
1208 if (match && match->str)
1209 return (match->str);
1210 else
1211 return (m);
1212 }
1213
1214
1215 /*
1216 * '_cupsMessageNew()' - Make a new message catalog array.
1217 */
1218
1219 cups_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
1229 /*
1230 * '_cupsMessageSave()' - Save a message catalog array.
1231 */
1232
1233 int /* 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
1280 #ifdef __APPLE__
1281 /*
1282 * 'appleLangDefault()' - Get the default locale string.
1283 */
1284
1285 static const char * /* O - Locale string */
1286 appleLangDefault(void)
1287 {
1288 CFBundleRef bundle; /* Main bundle (if any) */
1289 CFArrayRef bundleList; /* List of localizations in bundle */
1290 CFPropertyListRef localizationList = NULL;
1291 /* List of localization data */
1292 CFStringRef languageName; /* Current name */
1293 char *lang; /* LANG environment variable */
1294 _cups_globals_t *cg = _cupsGlobals();
1295 /* Pointer to library globals */
1296
1297
1298 DEBUG_puts("2appleLangDefault()");
1299
1300 /*
1301 * Only do the lookup and translation the first time.
1302 */
1303
1304 if (!cg->language[0])
1305 {
1306 if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL)
1307 {
1308 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang));
1309 strlcpy(cg->language, lang, sizeof(cg->language));
1310 return (cg->language);
1311 }
1312 else if ((bundle = CFBundleGetMainBundle()) != NULL &&
1313 (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
1314 {
1315 CFURLRef resources = CFBundleCopyResourcesDirectoryURL(bundle);
1316
1317 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
1318
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
1330 CFStringGetCString(cfpath, path, sizeof(path), kCFStringEncodingUTF8);
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.");
1346
1347 CFRelease(bundleList);
1348 }
1349
1350 if (!localizationList)
1351 {
1352 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
1353
1354 localizationList =
1355 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1356 kCFPreferencesCurrentApplication);
1357 }
1358
1359 if (localizationList)
1360 {
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
1369 if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
1370 CFArrayGetCount(localizationList) > 0)
1371 {
1372 languageName = CFArrayGetValueAtIndex(localizationList, 0);
1373
1374 if (languageName &&
1375 CFGetTypeID(languageName) == CFStringGetTypeID())
1376 {
1377 if (_cupsAppleLocale(languageName, cg->language, sizeof(cg->language)))
1378 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
1379 cg->language));
1380 else
1381 DEBUG_puts("3appleLangDefault: Unable to get locale.");
1382 }
1383 }
1384
1385 CFRelease(localizationList);
1386 }
1387
1388 /*
1389 * If we didn't find the language, default to en_US...
1390 */
1391
1392 if (!cg->language[0])
1393 {
1394 DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
1395 strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
1396 }
1397 }
1398 else
1399 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg->language));
1400
1401 /*
1402 * Return the cached locale...
1403 */
1404
1405 return (cg->language);
1406 }
1407
1408
1409 # ifdef CUPS_BUNDLEDIR
1410 /*
1411 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1412 */
1413
1414 static cups_array_t * /* O - Message catalog */
1415 appleMessageLoad(const char *locale) /* I - Locale ID */
1416 {
1417 char filename[1024], /* Path to cups.strings file */
1418 applelang[256], /* Apple language ID */
1419 baselang[4]; /* Base language */
1420 CFURLRef url; /* URL to cups.strings file */
1421 CFReadStreamRef stream = NULL; /* File stream */
1422 CFPropertyListRef plist = NULL; /* Localization file */
1423 #ifdef DEBUG
1424 const char *cups_strings = getenv("CUPS_STRINGS");
1425 /* Test strings file */
1426 CFErrorRef error = NULL; /* Error when opening file */
1427 #endif /* DEBUG */
1428
1429
1430 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale));
1431
1432 /*
1433 * Load the cups.strings file...
1434 */
1435
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
1445 snprintf(filename, sizeof(filename),
1446 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings",
1447 _cupsAppleLanguage(locale, applelang, sizeof(applelang)));
1448
1449 if (access(filename, 0))
1450 {
1451 /*
1452 * <rdar://problem/22086642>
1453 *
1454 * Try with original locale string...
1455 */
1456
1457 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
1458 snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
1459 }
1460
1461 if (access(filename, 0))
1462 {
1463 /*
1464 * <rdar://problem/25292403>
1465 *
1466 * Try with just the language code...
1467 */
1468
1469 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
1470
1471 strlcpy(baselang, locale, sizeof(baselang));
1472 if (baselang[3] == '-' || baselang[3] == '_')
1473 baselang[3] = '\0';
1474
1475 snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", baselang);
1476 }
1477
1478 if (access(filename, 0))
1479 {
1480 /*
1481 * Try alternate lproj directory names...
1482 */
1483
1484 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
1485
1486 if (!strncmp(locale, "en", 2))
1487 locale = "English";
1488 else if (!strncmp(locale, "nb", 2))
1489 locale = "no";
1490 else if (!strncmp(locale, "nl", 2))
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";
1502 else if (!strcmp(locale, "zh_HK") || !strncasecmp(locale, "zh-Hant", 7) || !strncasecmp(locale, "zh_Hant", 7))
1503 {
1504 /*
1505 * <rdar://problem/22130168>
1506 * <rdar://problem/27245567>
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 }
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));
1523 if (baselang[2] == '-' || baselang[2] == '_')
1524 baselang[2] = '\0';
1525
1526 locale = baselang;
1527 }
1528
1529 snprintf(filename, sizeof(filename),
1530 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
1531 }
1532
1533 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename));
1534
1535 url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
1536 (UInt8 *)filename,
1537 (CFIndex)strlen(filename), false);
1538 if (url)
1539 {
1540 stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
1541 if (stream)
1542 {
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
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
1565 CFRelease(msg);
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 }
1586
1587 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url, stream,
1588 plist));
1589
1590 /*
1591 * Create and return an empty array to act as a cache for messages, passing the
1592 * plist as the user data.
1593 */
1594
1595 return (_cupsMessageNew((void *)plist));
1596 }
1597 # endif /* CUPS_BUNDLEDIR */
1598 #endif /* __APPLE__ */
1599
1600
1601 /*
1602 * 'cups_cache_lookup()' - Lookup a language in the cache...
1603 */
1604
1605 static cups_lang_t * /* O - Language data or NULL */
1606 cups_cache_lookup(
1607 const char *name, /* I - Name of locale */
1608 cups_encoding_t encoding) /* I - Encoding of locale */
1609 {
1610 cups_lang_t *lang; /* Current language */
1611
1612
1613 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name,
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
1621 for (lang = lang_cache; lang != NULL; lang = lang->next)
1622 {
1623 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1624 "encoding=%d(%s)", (void *)lang, lang->language, lang->encoding,
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
1632 DEBUG_puts("8cups_cache_lookup: returning match!");
1633
1634 return (lang);
1635 }
1636 }
1637
1638 DEBUG_puts("8cups_cache_lookup: returning NULL!");
1639
1640 return (NULL);
1641 }
1642
1643
1644 /*
1645 * 'cups_message_compare()' - Compare two messages.
1646 */
1647
1648 static int /* O - Result of comparison */
1649 cups_message_compare(
1650 _cups_message_t *m1, /* I - First message */
1651 _cups_message_t *m2) /* I - Second message */
1652 {
1653 return (strcmp(m1->msg, m2->msg));
1654 }
1655
1656
1657 /*
1658 * 'cups_message_free()' - Free a message.
1659 */
1660
1661 static void
1662 cups_message_free(_cups_message_t *m) /* I - Message */
1663 {
1664 if (m->msg)
1665 free(m->msg);
1666
1667 if (m->str)
1668 free(m->str);
1669
1670 free(m);
1671 }
1672
1673
1674 /*
1675 * 'cups_message_load()' - Load the message catalog for a language.
1676 */
1677
1678 static void
1679 cups_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
1719 lang->strings = _cupsMessageLoad(filename, _CUPS_MESSAGE_UNQUOTE);
1720 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1721 }
1722
1723
1724 /*
1725 * 'cups_message_puts()' - Write a message string with quoting.
1726 */
1727
1728 static void
1729 cups_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
1762 /*
1763 * 'cups_read_strings()' - Read a pair of strings from a .strings file.
1764 */
1765
1766 static int /* O - 1 on success, 0 on failure */
1767 cups_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
1879 /*
1880 * 'cups_unquote()' - Unquote characters in strings...
1881 */
1882
1883 static void
1884 cups_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 }
1901
1902 d ++;
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 }