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