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