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