Update changelog
[platform/upstream/cups.git] / cups / language.c
1 /*
2  * "$Id: language.c 11173 2013-07-23 12:31:34Z msweet $"
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 || !*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 11173 2013-07-23 12:31:34Z msweet $".
1567  */