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