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