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