]> git.ipfire.org Git - thirdparty/cups.git/blob - cups/language.c
Merge changes from CUPS 1.7svn-r10629.
[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 strlcpy(charset, "UTF8", sizeof(charset));
450
451 /*
452 * Apple's setlocale doesn't give us the user's localization
453 * preference so we have to look it up this way...
454 */
455
456 if (!language)
457 {
458 if (!getenv("SOFTWARE") || (language = getenv("LANG")) == NULL)
459 language = appleLangDefault();
460
461 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language));
462 }
463
464 #else
465 /*
466 * Set the charset to "unknown"...
467 */
468
469 charset[0] = '\0';
470
471 /*
472 * Use setlocale() to determine the currently set locale, and then
473 * fallback to environment variables to avoid setting the locale,
474 * since setlocale() is not thread-safe!
475 */
476
477 if (!language)
478 {
479 /*
480 * First see if the locale has been set; if it is still "C" or
481 * "POSIX", use the environment to get the default...
482 */
483
484 # ifdef LC_MESSAGES
485 ptr = setlocale(LC_MESSAGES, NULL);
486 # else
487 ptr = setlocale(LC_ALL, NULL);
488 # endif /* LC_MESSAGES */
489
490 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr));
491
492 if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
493 {
494 /*
495 * Get the character set from the LC_CTYPE locale setting...
496 */
497
498 if ((ptr = getenv("LC_CTYPE")) == NULL)
499 if ((ptr = getenv("LC_ALL")) == NULL)
500 if ((ptr = getenv("LANG")) == NULL)
501 ptr = "en_US";
502
503 if ((csptr = strchr(ptr, '.')) != NULL)
504 {
505 /*
506 * Extract the character set from the environment...
507 */
508
509 for (ptr = charset, csptr ++; *csptr; csptr ++)
510 if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr))
511 *ptr++ = *csptr;
512
513 *ptr = '\0';
514 }
515
516 /*
517 * Get the locale for messages from the LC_MESSAGES locale setting...
518 */
519
520 if ((ptr = getenv("LC_MESSAGES")) == NULL)
521 if ((ptr = getenv("LC_ALL")) == NULL)
522 if ((ptr = getenv("LANG")) == NULL)
523 ptr = "en_US";
524 }
525
526 if (ptr)
527 {
528 strlcpy(locale, ptr, sizeof(locale));
529 language = locale;
530
531 /*
532 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
533 */
534
535 if (!strncmp(locale, "nb", 2))
536 locale[1] = 'o';
537
538 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language));
539 }
540 }
541 #endif /* __APPLE__ */
542
543 /*
544 * If "language" is NULL at this point, then chances are we are using
545 * a language that is not installed for the base OS.
546 */
547
548 if (!language)
549 {
550 /*
551 * Switch to the POSIX ("C") locale...
552 */
553
554 language = "C";
555 }
556
557 #ifdef CODESET
558 /*
559 * On systems that support the nl_langinfo(CODESET) call, use
560 * this value as the character set...
561 */
562
563 if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
564 {
565 /*
566 * Copy all of the letters and numbers in the CODESET string...
567 */
568
569 for (ptr = charset; *csptr; csptr ++)
570 if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1))
571 *ptr++ = *csptr;
572
573 *ptr = '\0';
574
575 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
576 "nl_langinfo(CODESET)...", charset));
577 }
578 #endif /* CODESET */
579
580 /*
581 * If we don't have a character set by now, default to UTF-8...
582 */
583
584 if (!charset[0])
585 strlcpy(charset, "UTF8", sizeof(charset));
586
587 /*
588 * Parse the language string passed in to a locale string. "C" is the
589 * standard POSIX locale and is copied unchanged. Otherwise the
590 * language string is converted from ll-cc[.charset] (language-country)
591 * to ll_CC[.CHARSET] to match the file naming convention used by all
592 * POSIX-compliant operating systems. Invalid language names are mapped
593 * to the POSIX locale.
594 */
595
596 country[0] = '\0';
597
598 if (language == NULL || !language[0] ||
599 !strcmp(language, "POSIX"))
600 strlcpy(langname, "C", sizeof(langname));
601 else
602 {
603 /*
604 * Copy the parts of the locale string over safely...
605 */
606
607 for (ptr = langname; *language; language ++)
608 if (*language == '_' || *language == '-' || *language == '.')
609 break;
610 else if (ptr < (langname + sizeof(langname) - 1))
611 *ptr++ = tolower(*language & 255);
612
613 *ptr = '\0';
614
615 if (*language == '_' || *language == '-')
616 {
617 /*
618 * Copy the country code...
619 */
620
621 for (language ++, ptr = country; *language; language ++)
622 if (*language == '.')
623 break;
624 else if (ptr < (country + sizeof(country) - 1))
625 *ptr++ = toupper(*language & 255);
626
627 *ptr = '\0';
628 }
629
630 if (*language == '.' && !charset[0])
631 {
632 /*
633 * Copy the encoding...
634 */
635
636 for (language ++, ptr = charset; *language; language ++)
637 if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1))
638 *ptr++ = toupper(*language & 255);
639
640 *ptr = '\0';
641 }
642
643 /*
644 * Force a POSIX locale for an invalid language name...
645 */
646
647 if (strlen(langname) != 2)
648 {
649 strlcpy(langname, "C", sizeof(langname));
650 country[0] = '\0';
651 charset[0] = '\0';
652 }
653 }
654
655 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
656 langname, country, charset));
657
658 /*
659 * Figure out the desired encoding...
660 */
661
662 encoding = CUPS_AUTO_ENCODING;
663
664 if (charset[0])
665 {
666 for (i = 0;
667 i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
668 i ++)
669 if (!_cups_strcasecmp(charset, locale_encodings[i]))
670 {
671 encoding = (cups_encoding_t)i;
672 break;
673 }
674
675 if (encoding == CUPS_AUTO_ENCODING)
676 {
677 /*
678 * Map alternate names for various character sets...
679 */
680
681 if (!_cups_strcasecmp(charset, "iso-2022-jp") ||
682 !_cups_strcasecmp(charset, "sjis"))
683 encoding = CUPS_WINDOWS_932;
684 else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
685 encoding = CUPS_WINDOWS_936;
686 else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
687 encoding = CUPS_WINDOWS_949;
688 else if (!_cups_strcasecmp(charset, "big5"))
689 encoding = CUPS_WINDOWS_950;
690 }
691 }
692
693 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding,
694 encoding == CUPS_AUTO_ENCODING ? "auto" :
695 lang_encodings[encoding]));
696
697 /*
698 * See if we already have this language/country loaded...
699 */
700
701 if (country[0])
702 snprintf(real, sizeof(real), "%s_%s", langname, country);
703 else
704 strlcpy(real, langname, sizeof(real));
705
706 _cupsMutexLock(&lang_mutex);
707
708 if ((lang = cups_cache_lookup(real, encoding)) != NULL)
709 {
710 _cupsMutexUnlock(&lang_mutex);
711
712 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real));
713
714 return (lang);
715 }
716
717 /*
718 * See if there is a free language available; if so, use that
719 * record...
720 */
721
722 for (lang = lang_cache; lang != NULL; lang = lang->next)
723 if (lang->used == 0)
724 break;
725
726 if (lang == NULL)
727 {
728 /*
729 * Allocate memory for the language and add it to the cache.
730 */
731
732 if ((lang = calloc(sizeof(cups_lang_t), 1)) == NULL)
733 {
734 _cupsMutexUnlock(&lang_mutex);
735
736 return (NULL);
737 }
738
739 lang->next = lang_cache;
740 lang_cache = lang;
741 }
742 else
743 {
744 /*
745 * Free all old strings as needed...
746 */
747
748 _cupsMessageFree(lang->strings);
749 lang->strings = NULL;
750 }
751
752 /*
753 * Then assign the language and encoding fields...
754 */
755
756 lang->used ++;
757 strlcpy(lang->language, real, sizeof(lang->language));
758
759 if (encoding != CUPS_AUTO_ENCODING)
760 lang->encoding = encoding;
761 else
762 lang->encoding = CUPS_UTF8;
763
764 /*
765 * Return...
766 */
767
768 _cupsMutexUnlock(&lang_mutex);
769
770 return (lang);
771 }
772
773
774 /*
775 * '_cupsLangString()' - Get a message string.
776 *
777 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
778 * convert the string to the language encoding.
779 */
780
781 const char * /* O - Localized message */
782 _cupsLangString(cups_lang_t *lang, /* I - Language */
783 const char *message) /* I - Message */
784 {
785 const char *s; /* Localized message */
786
787 /*
788 * Range check input...
789 */
790
791 if (!lang || !message || !*message)
792 return (message);
793
794 _cupsMutexLock(&lang_mutex);
795
796 /*
797 * Load the message catalog if needed...
798 */
799
800 if (!lang->strings)
801 cups_message_load(lang);
802
803 s = _cupsMessageLookup(lang->strings, message);
804
805 _cupsMutexUnlock(&lang_mutex);
806
807 return (s);
808 }
809
810
811 /*
812 * '_cupsMessageFree()' - Free a messages array.
813 */
814
815 void
816 _cupsMessageFree(cups_array_t *a) /* I - Message array */
817 {
818 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
819 /*
820 * Release the cups.strings dictionary as needed...
821 */
822
823 if (cupsArrayUserData(a))
824 CFRelease((CFDictionaryRef)cupsArrayUserData(a));
825 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
826
827 /*
828 * Free the array...
829 */
830
831 cupsArrayDelete(a);
832 }
833
834
835 /*
836 * '_cupsMessageLoad()' - Load a .po file into a messages array.
837 */
838
839 cups_array_t * /* O - New message array */
840 _cupsMessageLoad(const char *filename, /* I - Message catalog to load */
841 int unquote) /* I - Unescape \foo in strings? */
842 {
843 cups_file_t *fp; /* Message file */
844 cups_array_t *a; /* Message array */
845 _cups_message_t *m; /* Current message */
846 char s[4096], /* String buffer */
847 *ptr, /* Pointer into buffer */
848 *temp; /* New string */
849 int length; /* Length of combined strings */
850 size_t ptrlen; /* Length of string */
851
852
853 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename));
854
855 /*
856 * Create an array to hold the messages...
857 */
858
859 if ((a = _cupsMessageNew(NULL)) == NULL)
860 {
861 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
862 return (NULL);
863 }
864
865 /*
866 * Open the message catalog file...
867 */
868
869 if ((fp = cupsFileOpen(filename, "r")) == NULL)
870 {
871 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
872 strerror(errno)));
873 return (a);
874 }
875
876 /*
877 * Read messages from the catalog file until EOF...
878 *
879 * The format is the GNU gettext .po format, which is fairly simple:
880 *
881 * msgid "some text"
882 * msgstr "localized text"
883 *
884 * The ID and localized text can span multiple lines using the form:
885 *
886 * msgid ""
887 * "some long text"
888 * msgstr ""
889 * "localized text spanning "
890 * "multiple lines"
891 */
892
893 m = NULL;
894
895 while (cupsFileGets(fp, s, sizeof(s)) != NULL)
896 {
897 /*
898 * Skip blank and comment lines...
899 */
900
901 if (s[0] == '#' || !s[0])
902 continue;
903
904 /*
905 * Strip the trailing quote...
906 */
907
908 if ((ptr = strrchr(s, '\"')) == NULL)
909 continue;
910
911 *ptr = '\0';
912
913 /*
914 * Find start of value...
915 */
916
917 if ((ptr = strchr(s, '\"')) == NULL)
918 continue;
919
920 ptr ++;
921
922 /*
923 * Unquote the text...
924 */
925
926 if (unquote)
927 cups_unquote(ptr, ptr);
928
929 /*
930 * Create or add to a message...
931 */
932
933 if (!strncmp(s, "msgid", 5))
934 {
935 /*
936 * Add previous message as needed...
937 */
938
939 if (m)
940 {
941 if (m->str && m->str[0])
942 {
943 cupsArrayAdd(a, m);
944 }
945 else
946 {
947 /*
948 * Translation is empty, don't add it... (STR #4033)
949 */
950
951 free(m->id);
952 if (m->str)
953 free(m->str);
954 free(m);
955 }
956 }
957
958 /*
959 * Create a new message with the given msgid string...
960 */
961
962 if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
963 {
964 cupsFileClose(fp);
965 return (a);
966 }
967
968 if ((m->id = strdup(ptr)) == NULL)
969 {
970 free(m);
971 cupsFileClose(fp);
972 return (a);
973 }
974 }
975 else if (s[0] == '\"' && m)
976 {
977 /*
978 * Append to current string...
979 */
980
981 length = (int)strlen(m->str ? m->str : m->id);
982 ptrlen = strlen(ptr);
983
984 if ((temp = realloc(m->str ? m->str : m->id,
985 length + ptrlen + 1)) == NULL)
986 {
987 if (m->str)
988 free(m->str);
989 free(m->id);
990 free(m);
991
992 cupsFileClose(fp);
993 return (a);
994 }
995
996 if (m->str)
997 {
998 /*
999 * Copy the new portion to the end of the msgstr string - safe
1000 * to use memcpy because the buffer is allocated to the correct
1001 * size...
1002 */
1003
1004 m->str = temp;
1005
1006 memcpy(m->str + length, ptr, ptrlen + 1);
1007 }
1008 else
1009 {
1010 /*
1011 * Copy the new portion to the end of the msgid string - safe
1012 * to use memcpy because the buffer is allocated to the correct
1013 * size...
1014 */
1015
1016 m->id = temp;
1017
1018 memcpy(m->id + length, ptr, ptrlen + 1);
1019 }
1020 }
1021 else if (!strncmp(s, "msgstr", 6) && m)
1022 {
1023 /*
1024 * Set the string...
1025 */
1026
1027 if ((m->str = strdup(ptr)) == NULL)
1028 {
1029 free(m->id);
1030 free(m);
1031
1032 cupsFileClose(fp);
1033 return (a);
1034 }
1035 }
1036 }
1037
1038 /*
1039 * Add the last message string to the array as needed...
1040 */
1041
1042 if (m)
1043 {
1044 if (m->str && m->str[0])
1045 {
1046 cupsArrayAdd(a, m);
1047 }
1048 else
1049 {
1050 /*
1051 * Translation is empty, don't add it... (STR #4033)
1052 */
1053
1054 free(m->id);
1055 if (m->str)
1056 free(m->str);
1057 free(m);
1058 }
1059 }
1060
1061 /*
1062 * Close the message catalog file and return the new array...
1063 */
1064
1065 cupsFileClose(fp);
1066
1067 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
1068 cupsArrayCount(a)));
1069
1070 return (a);
1071 }
1072
1073
1074 /*
1075 * '_cupsMessageLookup()' - Lookup a message string.
1076 */
1077
1078 const char * /* O - Localized message */
1079 _cupsMessageLookup(cups_array_t *a, /* I - Message array */
1080 const char *m) /* I - Message */
1081 {
1082 _cups_message_t key, /* Search key */
1083 *match; /* Matching message */
1084
1085
1086 /*
1087 * Lookup the message string; if it doesn't exist in the catalog,
1088 * then return the message that was passed to us...
1089 */
1090
1091 key.id = (char *)m;
1092 match = (_cups_message_t *)cupsArrayFind(a, &key);
1093
1094 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1095 if (!match && cupsArrayUserData(a))
1096 {
1097 /*
1098 * Try looking the string up in the cups.strings dictionary...
1099 */
1100
1101 CFDictionaryRef dict; /* cups.strings dictionary */
1102 CFStringRef cfm, /* Message as a CF string */
1103 cfstr; /* Localized text as a CF string */
1104
1105 dict = (CFDictionaryRef)cupsArrayUserData(a);
1106 cfm = CFStringCreateWithCString(kCFAllocatorDefault, m,
1107 kCFStringEncodingUTF8);
1108 match = calloc(1, sizeof(_cups_message_t));
1109 match->id = strdup(m);
1110 cfstr = cfm ? CFDictionaryGetValue(dict, cfm) : NULL;
1111
1112 if (cfstr)
1113 {
1114 char buffer[1024]; /* Message buffer */
1115
1116 CFStringGetCString(cfstr, buffer, sizeof(buffer), kCFStringEncodingUTF8);
1117 match->str = strdup(buffer);
1118
1119 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...",
1120 m, buffer));
1121 }
1122 else
1123 {
1124 match->str = strdup(m);
1125
1126 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m));
1127 }
1128
1129 cupsArrayAdd(a, match);
1130
1131 if (cfm)
1132 CFRelease(cfm);
1133 }
1134 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1135
1136 if (match && match->str)
1137 return (match->str);
1138 else
1139 return (m);
1140 }
1141
1142
1143 /*
1144 * '_cupsMessageNew()' - Make a new message catalog array.
1145 */
1146
1147 cups_array_t * /* O - Array */
1148 _cupsMessageNew(void *context) /* I - User data */
1149 {
1150 return (cupsArrayNew3((cups_array_func_t)cups_message_compare, context,
1151 (cups_ahash_func_t)NULL, 0,
1152 (cups_acopy_func_t)NULL,
1153 (cups_afree_func_t)cups_message_free));
1154 }
1155
1156
1157 #ifdef __APPLE__
1158 /*
1159 * 'appleLangDefault()' - Get the default locale string.
1160 */
1161
1162 static const char * /* O - Locale string */
1163 appleLangDefault(void)
1164 {
1165 int i; /* Looping var */
1166 CFBundleRef bundle; /* Main bundle (if any) */
1167 CFArrayRef bundleList; /* List of localizations in bundle */
1168 CFPropertyListRef localizationList;
1169 /* List of localization data */
1170 CFStringRef languageName; /* Current name */
1171 CFStringRef localeName; /* Canonical from of name */
1172 char *lang; /* LANG environment variable */
1173 _cups_globals_t *cg = _cupsGlobals();
1174 /* Pointer to library globals */
1175
1176
1177 DEBUG_puts("2appleLangDefault()");
1178
1179 /*
1180 * Only do the lookup and translation the first time.
1181 */
1182
1183 if (!cg->language[0])
1184 {
1185 if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL)
1186 {
1187 strlcpy(cg->language, lang, sizeof(cg->language));
1188 return (cg->language);
1189 }
1190 else if ((bundle = CFBundleGetMainBundle()) != NULL &&
1191 (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
1192 {
1193 localizationList =
1194 CFBundleCopyPreferredLocalizationsFromArray(bundleList);
1195
1196 CFRelease(bundleList);
1197 }
1198 else
1199 localizationList =
1200 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1201 kCFPreferencesCurrentApplication);
1202
1203 if (localizationList)
1204 {
1205 if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
1206 CFArrayGetCount(localizationList) > 0)
1207 {
1208 languageName = CFArrayGetValueAtIndex(localizationList, 0);
1209
1210 if (languageName &&
1211 CFGetTypeID(languageName) == CFStringGetTypeID())
1212 {
1213 localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(
1214 kCFAllocatorDefault, languageName);
1215
1216 if (localeName)
1217 {
1218 CFStringGetCString(localeName, cg->language, sizeof(cg->language),
1219 kCFStringEncodingASCII);
1220 CFRelease(localeName);
1221
1222 DEBUG_printf(("9appleLangDefault: cg->language=\"%s\"",
1223 cg->language));
1224
1225 /*
1226 * Map new language identifiers to locales...
1227 */
1228
1229 for (i = 0;
1230 i < (int)(sizeof(apple_language_locale) /
1231 sizeof(apple_language_locale[0]));
1232 i ++)
1233 {
1234 if (!strcmp(cg->language, apple_language_locale[i].language))
1235 {
1236 DEBUG_printf(("9appleLangDefault: mapping \"%s\" to \"%s\"...",
1237 cg->language, apple_language_locale[i].locale));
1238 strlcpy(cg->language, apple_language_locale[i].locale,
1239 sizeof(cg->language));
1240 break;
1241 }
1242 }
1243
1244 /*
1245 * Convert language subtag into region subtag...
1246 */
1247
1248 if (cg->language[2] == '-')
1249 cg->language[2] = '_';
1250
1251 if (!strchr(cg->language, '.'))
1252 strlcat(cg->language, ".UTF-8", sizeof(cg->language));
1253 }
1254 }
1255 }
1256
1257 CFRelease(localizationList);
1258 }
1259
1260 /*
1261 * If we didn't find the language, default to en_US...
1262 */
1263
1264 if (!cg->language[0])
1265 strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
1266 }
1267
1268 /*
1269 * Return the cached locale...
1270 */
1271
1272 return (cg->language);
1273 }
1274
1275
1276 # ifdef CUPS_BUNDLEDIR
1277 /*
1278 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1279 */
1280
1281 static cups_array_t * /* O - Message catalog */
1282 appleMessageLoad(const char *locale) /* I - Locale ID */
1283 {
1284 char filename[1024], /* Path to cups.strings file */
1285 applelang[256]; /* Apple language ID */
1286 CFURLRef url; /* URL to cups.strings file */
1287 CFReadStreamRef stream = NULL; /* File stream */
1288 CFPropertyListRef plist = NULL; /* Localization file */
1289 #ifdef DEBUG
1290 CFErrorRef error = NULL; /* Error when opening file */
1291 #endif /* DEBUG */
1292
1293
1294 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale));
1295
1296 /*
1297 * Load the cups.strings file...
1298 */
1299
1300 snprintf(filename, sizeof(filename),
1301 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings",
1302 _cupsAppleLanguage(locale, applelang, sizeof(applelang)));
1303 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename));
1304
1305 if (access(filename, 0))
1306 {
1307 /*
1308 * Try alternate lproj directory names...
1309 */
1310
1311 if (!strncmp(locale, "en", 2))
1312 locale = "English";
1313 else if (!strncmp(locale, "nb", 2) || !strncmp(locale, "nl", 2))
1314 locale = "Dutch";
1315 else if (!strncmp(locale, "fr", 2))
1316 locale = "French";
1317 else if (!strncmp(locale, "de", 2))
1318 locale = "German";
1319 else if (!strncmp(locale, "it", 2))
1320 locale = "Italian";
1321 else if (!strncmp(locale, "ja", 2))
1322 locale = "Japanese";
1323 else if (!strncmp(locale, "es", 2))
1324 locale = "Spanish";
1325
1326 snprintf(filename, sizeof(filename),
1327 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
1328 DEBUG_printf(("1appleMessageLoad: alternate filename=\"%s\"", filename));
1329 }
1330
1331 url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
1332 (UInt8 *)filename,
1333 strlen(filename), false);
1334 if (url)
1335 {
1336 stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
1337 if (stream)
1338 {
1339 /*
1340 * Read the property list containing the localization data.
1341 *
1342 * NOTE: This code currently generates a clang "potential leak"
1343 * warning, but the object is released in _cupsMessageFree().
1344 */
1345
1346 CFReadStreamOpen(stream);
1347
1348 #ifdef DEBUG
1349 plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0,
1350 kCFPropertyListImmutable, NULL,
1351 &error);
1352 if (error)
1353 {
1354 CFStringRef msg = CFErrorCopyDescription(error);
1355 /* Error message */
1356
1357 CFStringGetCString(msg, filename, sizeof(filename),
1358 kCFStringEncodingUTF8);
1359 DEBUG_printf(("1appleMessageLoad: %s", filename));
1360
1361 CFRelease(msg);
1362 CFRelease(error);
1363 }
1364
1365 #else
1366 plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0,
1367 kCFPropertyListImmutable, NULL,
1368 NULL);
1369 #endif /* DEBUG */
1370
1371 if (plist && CFGetTypeID(plist) != CFDictionaryGetTypeID())
1372 {
1373 CFRelease(plist);
1374 plist = NULL;
1375 }
1376
1377 CFRelease(stream);
1378 }
1379
1380 CFRelease(url);
1381 }
1382
1383 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url, stream,
1384 plist));
1385
1386 /*
1387 * Create and return an empty array to act as a cache for messages, passing the
1388 * plist as the user data.
1389 */
1390
1391 return (_cupsMessageNew((void *)plist));
1392 }
1393 # endif /* CUPS_BUNDLEDIR */
1394 #endif /* __APPLE__ */
1395
1396
1397 /*
1398 * 'cups_cache_lookup()' - Lookup a language in the cache...
1399 */
1400
1401 static cups_lang_t * /* O - Language data or NULL */
1402 cups_cache_lookup(
1403 const char *name, /* I - Name of locale */
1404 cups_encoding_t encoding) /* I - Encoding of locale */
1405 {
1406 cups_lang_t *lang; /* Current language */
1407
1408
1409 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name,
1410 encoding, encoding == CUPS_AUTO_ENCODING ? "auto" :
1411 lang_encodings[encoding]));
1412
1413 /*
1414 * Loop through the cache and return a match if found...
1415 */
1416
1417 for (lang = lang_cache; lang != NULL; lang = lang->next)
1418 {
1419 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1420 "encoding=%d(%s)", lang, lang->language, lang->encoding,
1421 lang_encodings[lang->encoding]));
1422
1423 if (!strcmp(lang->language, name) &&
1424 (encoding == CUPS_AUTO_ENCODING || encoding == lang->encoding))
1425 {
1426 lang->used ++;
1427
1428 DEBUG_puts("8cups_cache_lookup: returning match!");
1429
1430 return (lang);
1431 }
1432 }
1433
1434 DEBUG_puts("8cups_cache_lookup: returning NULL!");
1435
1436 return (NULL);
1437 }
1438
1439
1440 /*
1441 * 'cups_message_compare()' - Compare two messages.
1442 */
1443
1444 static int /* O - Result of comparison */
1445 cups_message_compare(
1446 _cups_message_t *m1, /* I - First message */
1447 _cups_message_t *m2) /* I - Second message */
1448 {
1449 return (strcmp(m1->id, m2->id));
1450 }
1451
1452
1453 /*
1454 * 'cups_message_free()' - Free a message.
1455 */
1456
1457 static void
1458 cups_message_free(_cups_message_t *m) /* I - Message */
1459 {
1460 if (m->id)
1461 free(m->id);
1462
1463 if (m->str)
1464 free(m->str);
1465
1466 free(m);
1467 }
1468
1469
1470 /*
1471 * 'cups_message_load()' - Load the message catalog for a language.
1472 */
1473
1474 static void
1475 cups_message_load(cups_lang_t *lang) /* I - Language */
1476 {
1477 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1478 lang->strings = appleMessageLoad(lang->language);
1479
1480 #else
1481 char filename[1024]; /* Filename for language locale file */
1482 _cups_globals_t *cg = _cupsGlobals();
1483 /* Pointer to library globals */
1484
1485
1486 snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
1487 lang->language, lang->language);
1488
1489 if (strchr(lang->language, '_') && access(filename, 0))
1490 {
1491 /*
1492 * Country localization not available, look for generic localization...
1493 */
1494
1495 snprintf(filename, sizeof(filename), "%s/%.2s/cups_%.2s.po", cg->localedir,
1496 lang->language, lang->language);
1497
1498 if (access(filename, 0))
1499 {
1500 /*
1501 * No generic localization, so use POSIX...
1502 */
1503
1504 DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename,
1505 strerror(errno)));
1506
1507 snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir);
1508 }
1509 }
1510
1511 /*
1512 * Read the strings from the file...
1513 */
1514
1515 lang->strings = _cupsMessageLoad(filename, 1);
1516 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1517 }
1518
1519
1520 /*
1521 * 'cups_unquote()' - Unquote characters in strings...
1522 */
1523
1524 static void
1525 cups_unquote(char *d, /* O - Unquoted string */
1526 const char *s) /* I - Original string */
1527 {
1528 while (*s)
1529 {
1530 if (*s == '\\')
1531 {
1532 s ++;
1533 if (isdigit(*s))
1534 {
1535 *d = 0;
1536
1537 while (isdigit(*s))
1538 {
1539 *d = *d * 8 + *s - '0';
1540 s ++;
1541 }
1542
1543 d ++;
1544 }
1545 else
1546 {
1547 if (*s == 'n')
1548 *d ++ = '\n';
1549 else if (*s == 'r')
1550 *d ++ = '\r';
1551 else if (*s == 't')
1552 *d ++ = '\t';
1553 else
1554 *d++ = *s;
1555
1556 s ++;
1557 }
1558 }
1559 else
1560 *d++ = *s++;
1561 }
1562
1563 *d = '\0';
1564 }
1565
1566
1567 /*
1568 * End of "$Id: language.c 7558 2008-05-12 23:46:44Z mike $".
1569 */