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