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