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