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