]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - cups/language.c
Migrate Windows conditional code to _WIN32 define.
[thirdparty/cups.git] / cups / language.c
index 435c84bc446a37526093cde32d009e9487ebf4b1..7c848c09cf5f62398c8d83969a316be164aa8af1 100644 (file)
@@ -1,64 +1,25 @@
 /*
- * "$Id$"
+ * I18N/language support for CUPS.
  *
- *   I18N/language support for the Common UNIX Printing System (CUPS).
+ * Copyright 2007-2017 by Apple Inc.
+ * Copyright 1997-2007 by Easy Software Products.
  *
- *   Copyright 1997-2006 by Easy Software Products.
- *
- *   These coded instructions, statements, and computer programs are the
- *   property of Easy Software Products and are protected by Federal
- *   copyright law.  Distribution and use rights are outlined in the file
- *   "LICENSE.txt" which should have been included with this file.  If this
- *   file is missing or damaged please contact Easy Software Products
- *   at:
- *
- *       Attn: CUPS Licensing Information
- *       Easy Software Products
- *       44141 Airport View Drive, Suite 204
- *       Hollywood, Maryland 20636 USA
- *
- *       Voice: (301) 373-9600
- *       EMail: cups-info@cups.org
- *         WWW: http://www.cups.org
- *
- *   This file is subject to the Apple OS-Developed Software exception.
- *
- * Contents:
- *
- *   _cupsEncodingName()    - Return the character encoding name string
- *                            for the given encoding enumeration.
- *   cupsLangDefault()      - Return the default language.
- *   cupsLangEncoding()     - Return the character encoding (us-ascii, etc.)
- *                            for the given language.
- *   cupsLangFlush()        - Flush all language data out of the cache.
- *   cupsLangFree()         - Free language data.
- *   cupsLangGet()          - Get a language.
- *   _cupsLangString()      - Get a message string.
- *   _cupsMessageFree()     - Free a messages array.
- *   _cupsMessageLoad()     - Load a .po file into a messages array.
- *   _cupsMessageLookup()   - Lookup a message string.
- *   appleLangDefault()     - Get the default locale string.
- *   cups_cache_lookup()    - Lookup a language in the cache...
- *   cups_message_compare() - Compare two messages.
- *   cups_unquote()         - Unquote characters in strings...
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
  */
 
 /*
  * Include necessary headers...
  */
 
-#include "globals.h"
-#include "debug.h"
-#include <stdlib.h>
-#include <errno.h>
+#include "cups-private.h"
 #ifdef HAVE_LANGINFO_H
 #  include <langinfo.h>
 #endif /* HAVE_LANGINFO_H */
-#ifdef WIN32
+#ifdef _WIN32
 #  include <io.h>
 #else
 #  include <unistd.h>
-#endif /* WIN32 */
+#endif /* _WIN32 */
 #ifdef HAVE_COREFOUNDATION_H
 #  include <CoreFoundation/CoreFoundation.h>
 #endif /* HAVE_COREFOUNDATION_H */
  * Local globals...
  */
 
-#ifdef HAVE_PTHREAD_H
-static pthread_mutex_t lang_mutex = PTHREAD_MUTEX_INITIALIZER;
+static _cups_mutex_t   lang_mutex = _CUPS_MUTEX_INITIALIZER;
                                        /* Mutex to control access to cache */
-#endif /* HAVE_PTHREAD_H */
 static cups_lang_t     *lang_cache = NULL;
                                        /* Language string cache */
-
-
-/*
- * Local functions...
- */
-
-#ifdef __APPLE__
-static const char      *appleLangDefault(void);
-#endif /* __APPLE__ */
-static cups_lang_t     *cups_cache_lookup(const char *name,
-                                          cups_encoding_t encoding);
-static int             cups_message_compare(_cups_message_t *m1,
-                                            _cups_message_t *m2);
-static void            cups_unquote(char *d, const char *s);
-
-
-/*
- * Local globals...
- */
-
 static const char * const lang_encodings[] =
                        {               /* Encoding strings */
                          "us-ascii",           "iso-8859-1",
@@ -103,14 +42,14 @@ static const char * const lang_encodings[] =
                          "iso-8859-8",         "iso-8859-9",
                          "iso-8859-10",        "utf-8",
                          "iso-8859-13",        "iso-8859-14",
-                         "iso-8859-15",        "windows-874",
-                         "windows-1250",       "windows-1251",
-                         "windows-1252",       "windows-1253",
-                         "windows-1254",       "windows-1255",
-                         "windows-1256",       "windows-1257",
-                         "windows-1258",       "koi8-r",
+                         "iso-8859-15",        "cp874",
+                         "cp1250",             "cp1251",
+                         "cp1252",             "cp1253",
+                         "cp1254",             "cp1255",
+                         "cp1256",             "cp1257",
+                         "cp1258",             "koi8-r",
                          "koi8-u",             "iso-8859-11",
-                         "iso-8859-16",        "mac-roman",
+                         "iso-8859-16",        "mac",
                          "unknown",            "unknown",
                          "unknown",            "unknown",
                          "unknown",            "unknown",
@@ -128,9 +67,9 @@ static const char * const lang_encodings[] =
                          "unknown",            "unknown",
                          "unknown",            "unknown",
                          "unknown",            "unknown",
-                         "windows-932",        "windows-936",
-                         "windows-949",        "windows-950",
-                         "windows-1361",       "unknown",
+                         "cp932",              "cp936",
+                         "cp949",              "cp950",
+                         "cp1361",             "unknown",
                          "unknown",            "unknown",
                          "unknown",            "unknown",
                          "unknown",            "unknown",
@@ -161,9 +100,234 @@ static const char * const lang_encodings[] =
                          "unknown",            "unknown",
                          "unknown",            "unknown",
                          "euc-cn",             "euc-jp",
-                         "euc-kr",             "euc-tw"
+                         "euc-kr",             "euc-tw",
+                         "shift_jisx0213"
                        };
 
+#ifdef __APPLE__
+typedef struct
+{
+  const char * const language;         /* Language ID */
+  const char * const locale;           /* Locale ID */
+} _apple_language_locale_t;
+
+static const _apple_language_locale_t apple_language_locale[] =
+{                                      /* Language to locale ID LUT */
+  { "en",         "en_US" },
+  { "nb",         "no" },
+  { "nb_NO",      "no" },
+  { "zh-Hans",    "zh_CN" },
+  { "zh_HANS",    "zh_CN" },
+  { "zh-Hant",    "zh_TW" },
+  { "zh_HANT",    "zh_TW" },
+  { "zh-Hant_CN", "zh_TW" }
+};
+#endif /* __APPLE__ */
+
+
+/*
+ * Local functions...
+ */
+
+
+#ifdef __APPLE__
+static const char      *appleLangDefault(void);
+#  ifdef CUPS_BUNDLEDIR
+#    ifndef CF_RETURNS_RETAINED
+#      if __has_feature(attribute_cf_returns_retained)
+#        define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
+#      else
+#        define CF_RETURNS_RETAINED
+#      endif /* __has_feature(attribute_cf_returns_retained) */
+#    endif /* !CF_RETURNED_RETAINED */
+static cups_array_t    *appleMessageLoad(const char *locale) CF_RETURNS_RETAINED;
+#  endif /* CUPS_BUNDLEDIR */
+#endif /* __APPLE__ */
+static cups_lang_t     *cups_cache_lookup(const char *name, cups_encoding_t encoding);
+static int             cups_message_compare(_cups_message_t *m1, _cups_message_t *m2);
+static void            cups_message_free(_cups_message_t *m);
+static void            cups_message_load(cups_lang_t *lang);
+static void            cups_message_puts(cups_file_t *fp, const char *s);
+static int             cups_read_strings(cups_file_t *fp, int flags, cups_array_t *a);
+static void            cups_unquote(char *d, const char *s);
+
+
+#ifdef __APPLE__
+/*
+ * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
+ *                          locale ID.
+ */
+
+const char *                           /* O - Language ID */
+_cupsAppleLanguage(const char *locale, /* I - Locale ID */
+                   char       *language,/* I - Language ID buffer */
+                   size_t     langsize)        /* I - Size of language ID buffer */
+{
+  int          i;                      /* Looping var */
+  CFStringRef  localeid,               /* CF locale identifier */
+               langid;                 /* CF language identifier */
+
+
+ /*
+  * Copy the locale name and convert, as needed, to the Apple-specific
+  * locale identifier...
+  */
+
+  switch (strlen(locale))
+  {
+    default :
+        /*
+        * Invalid locale...
+        */
+
+        strlcpy(language, "en", langsize);
+        break;
+
+    case 2 :
+        strlcpy(language, locale, langsize);
+        break;
+
+    case 5 :
+        strlcpy(language, locale, langsize);
+
+       if (language[2] == '-')
+       {
+        /*
+         * Convert ll-cc to ll_CC...
+         */
+
+         language[2] = '_';
+         language[3] = (char)toupper(language[3] & 255);
+         language[4] = (char)toupper(language[4] & 255);
+       }
+       break;
+  }
+
+  for (i = 0;
+       i < (int)(sizeof(apple_language_locale) /
+                sizeof(apple_language_locale[0]));
+       i ++)
+    if (!strcmp(locale, apple_language_locale[i].locale))
+    {
+      strlcpy(language, apple_language_locale[i].language, sizeof(language));
+      break;
+    }
+
+ /*
+  * Attempt to map the locale ID to a language ID...
+  */
+
+  if ((localeid = CFStringCreateWithCString(kCFAllocatorDefault, language,
+                                            kCFStringEncodingASCII)) != NULL)
+  {
+    if ((langid = CFLocaleCreateCanonicalLanguageIdentifierFromString(
+                      kCFAllocatorDefault, localeid)) != NULL)
+    {
+      CFStringGetCString(langid, language, (CFIndex)langsize, kCFStringEncodingASCII);
+      CFRelease(langid);
+    }
+
+    CFRelease(localeid);
+  }
+
+ /*
+  * Return what we got...
+  */
+
+  return (language);
+}
+
+
+/*
+ * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID.
+ */
+
+const char *                                   /* O - Locale */
+_cupsAppleLocale(CFStringRef languageName,     /* I - Apple language ID */
+                 char        *locale,          /* I - Buffer for locale */
+                size_t      localesize)        /* I - Size of buffer */
+{
+  int          i;                      /* Looping var */
+  CFStringRef  localeName;             /* Locale as a CF string */
+#ifdef DEBUG
+  char          temp[1024];             /* Temporary string */
+
+
+  if (!CFStringGetCString(languageName, temp, (CFIndex)sizeof(temp), kCFStringEncodingASCII))
+    temp[0] = '\0';
+
+  DEBUG_printf(("_cupsAppleLocale(languageName=%p(%s), locale=%p, localsize=%d)", (void *)languageName, temp, (void *)locale, (int)localesize));
+#endif /* DEBUG */
+
+  localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, languageName);
+
+  if (localeName)
+  {
+   /*
+    * Copy the locale name and tweak as needed...
+    */
+
+    if (!CFStringGetCString(localeName, locale, (CFIndex)localesize, kCFStringEncodingASCII))
+      *locale = '\0';
+
+    DEBUG_printf(("_cupsAppleLocale: locale=\"%s\"", locale));
+
+    CFRelease(localeName);
+
+   /*
+    * Map new language identifiers to locales...
+    */
+
+    for (i = 0;
+        i < (int)(sizeof(apple_language_locale) /
+                  sizeof(apple_language_locale[0]));
+        i ++)
+    {
+      size_t len = strlen(apple_language_locale[i].language);
+
+      if (!strcmp(locale, apple_language_locale[i].language) ||
+          (!strncmp(locale, apple_language_locale[i].language, len) && (locale[len] == '_' || locale[len] == '-')))
+      {
+        DEBUG_printf(("_cupsAppleLocale: Updating locale to \"%s\".", apple_language_locale[i].locale));
+       strlcpy(locale, apple_language_locale[i].locale, localesize);
+       break;
+      }
+    }
+  }
+  else
+  {
+   /*
+    * Just try the Apple language name...
+    */
+
+    if (!CFStringGetCString(languageName, locale, (CFIndex)localesize, kCFStringEncodingASCII))
+      *locale = '\0';
+  }
+
+  if (!*locale)
+  {
+    DEBUG_puts("_cupsAppleLocale: Returning NULL.");
+    return (NULL);
+  }
+
+ /*
+  * Convert language subtag into region subtag...
+  */
+
+  if (locale[2] == '-')
+    locale[2] = '_';
+  else if (locale[3] == '-')
+    locale[3] = '_';
+
+  if (!strchr(locale, '.'))
+    strlcat(locale, ".UTF-8", localesize);
+
+  DEBUG_printf(("_cupsAppleLocale: Returning \"%s\".", locale));
+
+  return (locale);
+}
+#endif /* __APPLE__ */
+
 
 /*
  * '_cupsEncodingName()' - Return the character encoding name string
@@ -174,11 +338,19 @@ const char *                              /* O - Character encoding */
 _cupsEncodingName(
     cups_encoding_t encoding)          /* I - Encoding value */
 {
-  if (encoding < 0 ||
-      encoding >= (sizeof(lang_encodings) / sizeof(const char *)))
+  if (encoding < CUPS_US_ASCII ||
+      encoding >= (cups_encoding_t)(sizeof(lang_encodings) / sizeof(lang_encodings[0])))
+  {
+    DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
+                  encoding, lang_encodings[0]));
     return (lang_encodings[0]);
+  }
   else
+  {
+    DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
+                  encoding, lang_encodings[encoding]));
     return (lang_encodings[encoding]);
+  }
 }
 
 
@@ -223,9 +395,7 @@ cupsLangFlush(void)
   * Free all languages in the cache...
   */
 
-#ifdef HAVE_PTHREAD_H
-  pthread_mutex_lock(&lang_mutex);
-#endif /* HAVE_PTHREAD_H */
+  _cupsMutexLock(&lang_mutex);
 
   for (lang = lang_cache; lang != NULL; lang = next)
   {
@@ -245,31 +415,25 @@ cupsLangFlush(void)
 
   lang_cache = NULL;
 
-#ifdef HAVE_PTHREAD_H
-  pthread_mutex_unlock(&lang_mutex);
-#endif /* HAVE_PTHREAD_H */
+  _cupsMutexUnlock(&lang_mutex);
 }
 
 
 /*
  * 'cupsLangFree()' - Free language data.
  *
- * This does not actually free anything; use cupsLangFlush() for that.
+ * This does not actually free anything; use @link cupsLangFlush@ for that.
  */
 
 void
 cupsLangFree(cups_lang_t *lang)                /* I - Language to free */
 {
-#ifdef HAVE_PTHREAD_H
-  pthread_mutex_lock(&lang_mutex);
-#endif /* HAVE_PTHREAD_H */
+  _cupsMutexLock(&lang_mutex);
 
   if (lang != NULL && lang->used > 0)
     lang->used --;
 
-#ifdef HAVE_PTHREAD_H
-  pthread_mutex_unlock(&lang_mutex);
-#endif /* HAVE_PTHREAD_H */
+  _cupsMutexUnlock(&lang_mutex);
 }
 
 
@@ -289,12 +453,9 @@ cupsLangGet(const char *language)  /* I - Language or locale */
                        charset[16],    /* Character set */
                        *csptr,         /* Pointer to CODESET string */
                        *ptr,           /* Pointer into language/charset */
-                       real[48],       /* Real language name */
-                       filename[1024]; /* Filename for language locale file */
+                       real[48];       /* Real language name */
   cups_encoding_t      encoding;       /* Encoding to use */
   cups_lang_t          *lang;          /* Current language... */
-  _cups_globals_t      *cg = _cupsGlobals();
-                                       /* Pointer to library globals */
   static const char * const locale_encodings[] =
                {                       /* Locale charset names */
                  "ASCII",      "ISO88591",     "ISO88592",     "ISO88593",
@@ -333,26 +494,32 @@ cupsLangGet(const char *language) /* I - Language or locale */
                  "",           "",             "",             "",
                  "",           "",             "",             "",
 
-                 "EUCCN",      "EUCJP",        "EUCKR",        "EUCTW"
+                 "EUCCN",      "EUCJP",        "EUCKR",        "EUCTW",
+                 "SHIFT_JISX0213"
                };
 
 
-  DEBUG_printf(("cupsLangGet(language=\"%s\")\n", language ? language : "(null)"));
+  DEBUG_printf(("2cupsLangGet(language=\"%s\")", language));
 
 #ifdef __APPLE__
  /*
   * Set the character set to UTF-8...
   */
 
-  strcpy(charset, "UTF8");
+  strlcpy(charset, "UTF8", sizeof(charset));
 
  /*
-  * Apple's setlocale doesn't give us the user's localization 
+  * Apple's setlocale doesn't give us the user's localization
   * preference so we have to look it up this way...
   */
 
   if (!language)
-    language = appleLangDefault();
+  {
+    if (!getenv("SOFTWARE") || (language = getenv("LANG")) == NULL)
+      language = appleLangDefault();
+
+    DEBUG_printf(("4cupsLangGet: language=\"%s\"", language));
+  }
 
 #else
  /*
@@ -380,8 +547,7 @@ cupsLangGet(const char *language)   /* I - Language or locale */
     ptr = setlocale(LC_ALL, NULL);
 #  endif /* LC_MESSAGES */
 
-    DEBUG_printf(("cupsLangGet: current locale is \"%s\"\n",
-                  ptr ? ptr : "(null)"));
+    DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr));
 
     if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
     {
@@ -401,19 +567,11 @@ cupsLangGet(const char *language) /* I - Language or locale */
        */
 
        for (ptr = charset, csptr ++; *csptr; csptr ++)
-         if (ptr < (charset + sizeof(charset) - 1) && isalnum(*csptr & 255))
+         if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr))
            *ptr++ = *csptr;
 
         *ptr = '\0';
       }
-      else
-      {
-       /*
-        * Default to UTF-8...
-       */
-
-        strcpy(charset, "UTF8");
-      }
 
      /*
       * Get the locale for messages from the LC_MESSAGES locale setting...
@@ -430,8 +588,14 @@ cupsLangGet(const char *language)  /* I - Language or locale */
       strlcpy(locale, ptr, sizeof(locale));
       language = locale;
 
-      DEBUG_printf(("cupsLangGet: new language value is \"%s\"\n",
-                    language ? language : "(null)"));
+     /*
+      * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
+      */
+
+      if (!strncmp(locale, "nb", 2))
+        locale[1] = 'o';
+
+      DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language));
     }
   }
 #endif /* __APPLE__ */
@@ -463,16 +627,23 @@ cupsLangGet(const char *language) /* I - Language or locale */
     */
 
     for (ptr = charset; *csptr; csptr ++)
-      if (isalnum(*csptr & 255) && ptr < (charset + sizeof(charset) - 1))
+      if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1))
         *ptr++ = *csptr;
 
     *ptr = '\0';
 
-    DEBUG_printf(("cupsLangGet: charset set to \"%s\" via nl_langinfo(CODESET)...\n",
-                  charset));
+    DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
+                  "nl_langinfo(CODESET)...", charset));
   }
 #endif /* CODESET */
 
+ /*
+  * If we don't have a character set by now, default to UTF-8...
+  */
+
+  if (!charset[0])
+    strlcpy(charset, "UTF8", sizeof(charset));
+
  /*
   * Parse the language string passed in to a locale string. "C" is the
   * standard POSIX locale and is copied unchanged.  Otherwise the
@@ -486,7 +657,7 @@ cupsLangGet(const char *language)   /* I - Language or locale */
 
   if (language == NULL || !language[0] ||
       !strcmp(language, "POSIX"))
-    strcpy(langname, "C");
+    strlcpy(langname, "C", sizeof(langname));
   else
   {
    /*
@@ -497,7 +668,7 @@ cupsLangGet(const char *language)   /* I - Language or locale */
       if (*language == '_' || *language == '-' || *language == '.')
        break;
       else if (ptr < (langname + sizeof(langname) - 1))
-        *ptr++ = tolower(*language & 255);
+        *ptr++ = (char)tolower(*language & 255);
 
     *ptr = '\0';
 
@@ -511,9 +682,18 @@ cupsLangGet(const char *language)  /* I - Language or locale */
        if (*language == '.')
          break;
        else if (ptr < (country + sizeof(country) - 1))
-          *ptr++ = toupper(*language & 255);
+          *ptr++ = (char)toupper(*language & 255);
 
       *ptr = '\0';
+
+     /*
+      * Map Chinese region codes to legacy country codes.
+      */
+
+      if (!strcmp(language, "zh") && !strcmp(country, "HANS"))
+        strlcpy(country, "CN", sizeof(country));
+      if (!strcmp(language, "zh") && !strcmp(country, "HANT"))
+        strlcpy(country, "TW", sizeof(country));
     }
 
     if (*language == '.' && !charset[0])
@@ -523,8 +703,8 @@ cupsLangGet(const char *language)   /* I - Language or locale */
       */
 
       for (language ++, ptr = charset; *language; language ++)
-        if (isalnum(*language & 255) && ptr < (charset + sizeof(charset) - 1))
-          *ptr++ = toupper(*language & 255);
+        if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1))
+          *ptr++ = (char)toupper(*language & 255);
 
       *ptr = '\0';
     }
@@ -533,15 +713,15 @@ cupsLangGet(const char *language) /* I - Language or locale */
     * Force a POSIX locale for an invalid language name...
     */
 
-    if (strlen(langname) != 2)
+    if (strlen(langname) != 2 && strlen(langname) != 3)
     {
-      strcpy(langname, "C");
+      strlcpy(langname, "C", sizeof(langname));
       country[0] = '\0';
       charset[0] = '\0';
     }
   }
 
-  DEBUG_printf(("cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"\n",
+  DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
                 langname, country, charset));
 
  /*
@@ -555,14 +735,31 @@ cupsLangGet(const char *language) /* I - Language or locale */
     for (i = 0;
          i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
         i ++)
-      if (!strcasecmp(charset, locale_encodings[i]))
+      if (!_cups_strcasecmp(charset, locale_encodings[i]))
       {
        encoding = (cups_encoding_t)i;
        break;
       }
+
+    if (encoding == CUPS_AUTO_ENCODING)
+    {
+     /*
+      * Map alternate names for various character sets...
+      */
+
+      if (!_cups_strcasecmp(charset, "iso-2022-jp") ||
+          !_cups_strcasecmp(charset, "sjis"))
+       encoding = CUPS_WINDOWS_932;
+      else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
+       encoding = CUPS_WINDOWS_936;
+      else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
+       encoding = CUPS_WINDOWS_949;
+      else if (!_cups_strcasecmp(charset, "big5"))
+       encoding = CUPS_WINDOWS_950;
+    }
   }
 
-  DEBUG_printf(("cupsLangGet: encoding=%d(%s)\n", encoding,
+  DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding,
                 encoding == CUPS_AUTO_ENCODING ? "auto" :
                    lang_encodings[encoding]));
 
@@ -571,50 +768,19 @@ cupsLangGet(const char *language) /* I - Language or locale */
   */
 
   if (country[0])
-  {
     snprintf(real, sizeof(real), "%s_%s", langname, country);
-
-    snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
-             real, real);
-  }
   else
-  {
-    strcpy(real, langname);
-    filename[0] = '\0';                        /* anti-compiler-warning-code */
-  }
-
-#ifdef HAVE_PTHREAD_H
-  pthread_mutex_lock(&lang_mutex);
-#endif /* HAVE_PTHREAD_H */
-
-  if ((lang = cups_cache_lookup(langname, encoding)) != NULL)
-  {
-#ifdef HAVE_PTHREAD_H
-    pthread_mutex_unlock(&lang_mutex);
-#endif /* HAVE_PTHREAD_H */
+    strlcpy(real, langname, sizeof(real));
 
-    return (lang);
-  }
+  _cupsMutexLock(&lang_mutex);
 
-  if (!country[0] || access(filename, 0))
+  if ((lang = cups_cache_lookup(real, encoding)) != NULL)
   {
-   /*
-    * Country localization not available, look for generic localization...
-    */
-
-    snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
-             langname, langname);
-
-    if (access(filename, 0))
-    {
-     /*
-      * No generic localization, so use POSIX...
-      */
+    _cupsMutexUnlock(&lang_mutex);
 
-      DEBUG_printf(("access(\"%s\", 0): %s\n", filename, strerror(errno)));
+    DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real));
 
-      snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir);
-    }
+    return (lang);
   }
 
  /*
@@ -634,9 +800,7 @@ cupsLangGet(const char *language)   /* I - Language or locale */
 
     if ((lang = calloc(sizeof(cups_lang_t), 1)) == NULL)
     {
-#ifdef HAVE_PTHREAD_H
-      pthread_mutex_unlock(&lang_mutex);
-#endif /* HAVE_PTHREAD_H */
+      _cupsMutexUnlock(&lang_mutex);
 
       return (NULL);
     }
@@ -651,6 +815,7 @@ cupsLangGet(const char *language)   /* I - Language or locale */
     */
 
     _cupsMessageFree(lang->strings);
+    lang->strings = NULL;
   }
 
  /*
@@ -665,19 +830,11 @@ cupsLangGet(const char *language) /* I - Language or locale */
   else
     lang->encoding = CUPS_UTF8;
 
- /*
-  * Read the strings from the file...
-  */
-
-  lang->strings = _cupsMessageLoad(filename);
-
  /*
   * Return...
   */
 
-#ifdef HAVE_PTHREAD_H
-  pthread_mutex_unlock(&lang_mutex);
-#endif /* HAVE_PTHREAD_H */
+  _cupsMutexUnlock(&lang_mutex);
 
   return (lang);
 }
@@ -694,28 +851,32 @@ const char *                              /* O - Localized message */
 _cupsLangString(cups_lang_t *lang,     /* I - Language */
                 const char  *message)  /* I - Message */
 {
+  const char *s;                       /* Localized message */
+
+
+  DEBUG_printf(("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang, message));
+
  /*
   * Range check input...
   */
 
-  if (!lang || !message)
+  if (!lang || !message || !*message)
     return (message);
 
-#ifdef HAVE_PTHREAD_H
-  {
-    const char *s;                     /* Localized message */
+  _cupsMutexLock(&lang_mutex);
 
-    pthread_mutex_lock(&lang_mutex);
+ /*
+  * Load the message catalog if needed...
+  */
 
-    s = _cupsMessageLookup(lang->strings, message);
+  if (!lang->strings)
+    cups_message_load(lang);
 
-    pthread_mutex_unlock(&lang_mutex);
+  s = _cupsMessageLookup(lang->strings, message);
 
-    return (s);
-  }
-#else
-  return (_cupsMessageLookup(lang->strings, message));
-#endif /* HAVE_PTHREAD_H */
+  _cupsMutexUnlock(&lang_mutex);
+
+  return (s);
 }
 
 
@@ -726,27 +887,14 @@ _cupsLangString(cups_lang_t *lang,        /* I - Language */
 void
 _cupsMessageFree(cups_array_t *a)      /* I - Message array */
 {
-  _cups_message_t      *m;             /* Current message */
-
-
-  for (m = (_cups_message_t *)cupsArrayFirst(a);
-       m;
-       m = (_cups_message_t *)cupsArrayNext(a))
-  {
-   /*
-    * Remove the message from the array, then free the message and strings.
-    */
-
-    cupsArrayRemove(a, m);
-
-    if (m->id)
-      free(m->id);
-
-    if (m->str)
-      free(m->str);
+#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
+ /*
+  * Release the cups.strings dictionary as needed...
+  */
 
-    free(m);
-  }
+  if (cupsArrayUserData(a))
+    CFRelease((CFDictionaryRef)cupsArrayUserData(a));
+#endif /* __APPLE__ && CUPS_BUNDLEDIR */
 
  /*
   * Free the array...
@@ -757,11 +905,12 @@ _cupsMessageFree(cups_array_t *a) /* I - Message array */
 
 
 /*
- * '_cupsMessageLoad()' - Load a .po file into a messages array.
+ * '_cupsMessageLoad()' - Load a .po or .strings file into a messages array.
  */
 
 cups_array_t *                         /* O - New message array */
-_cupsMessageLoad(const char *filename) /* I - Message catalog to load */
+_cupsMessageLoad(const char *filename, /* I - Message catalog to load */
+                 int        flags)     /* I - Load flags */
 {
   cups_file_t          *fp;            /* Message file */
   cups_array_t         *a;             /* Message array */
@@ -769,165 +918,227 @@ _cupsMessageLoad(const char *filename)  /* I - Message catalog to load */
   char                 s[4096],        /* String buffer */
                        *ptr,           /* Pointer into buffer */
                        *temp;          /* New string */
-  int                  length;         /* Length of combined strings */
+  size_t               length,         /* Length of combined strings */
+                       ptrlen;         /* Length of string */
 
 
-  DEBUG_printf(("_cupsMessageLoad(filename=\"%s\")\n", filename));
+  DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename));
 
  /*
   * Create an array to hold the messages...
   */
 
-  if ((a = cupsArrayNew((cups_array_func_t)cups_message_compare, NULL)) == NULL)
+  if ((a = _cupsMessageNew(NULL)) == NULL)
+  {
+    DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
     return (NULL);
+  }
 
  /*
   * Open the message catalog file...
   */
 
   if ((fp = cupsFileOpen(filename, "r")) == NULL)
+  {
+    DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
+                  strerror(errno)));
     return (a);
+  }
 
- /*
-  * Read messages from the catalog file until EOF...
-  *
-  * The format is the GNU gettext .po format, which is fairly simple:
-  *
-  *     msgid "some text"
-  *     msgstr "localized text"
-  *
-  * The ID and localized text can span multiple lines using the form:
-  *
-  *     msgid ""
-  *     "some long text"
-  *     msgstr ""
-  *     "localized text spanning "
-  *     "multiple lines"
-  */
-
-  m = NULL;
-
-  while (cupsFileGets(fp, s, sizeof(s)) != NULL)
+  if (flags & _CUPS_MESSAGE_STRINGS)
+  {
+    while (cups_read_strings(fp, flags, a));
+  }
+  else
   {
    /*
-    * Skip blank and comment lines...
-    */
-
-    if (s[0] == '#' || !s[0])
-      continue;
-
-   /*
-    * Strip the trailing quote...
+    * Read messages from the catalog file until EOF...
+    *
+    * The format is the GNU gettext .po format, which is fairly simple:
+    *
+    *     msgid "some text"
+    *     msgstr "localized text"
+    *
+    * The ID and localized text can span multiple lines using the form:
+    *
+    *     msgid ""
+    *     "some long text"
+    *     msgstr ""
+    *     "localized text spanning "
+    *     "multiple lines"
     */
 
-    if ((ptr = strrchr(s, '\"')) == NULL)
-      continue;
-
-    *ptr = '\0';
+    m = NULL;
 
-   /*
-    * Find start of value...
-    */
-    
-    if ((ptr = strchr(s, '\"')) == NULL)
-      continue;
+    while (cupsFileGets(fp, s, sizeof(s)) != NULL)
+    {
+     /*
+      * Skip blank and comment lines...
+      */
 
-    ptr ++;
+      if (s[0] == '#' || !s[0])
+       continue;
 
-   /*
-    * Unquote the text...
-    */
+     /*
+      * Strip the trailing quote...
+      */
 
-    cups_unquote(ptr, ptr);
+      if ((ptr = strrchr(s, '\"')) == NULL)
+       continue;
 
-   /*
-    * Create or add to a message...
-    */
+      *ptr = '\0';
 
-    if (!strncmp(s, "msgid", 5))
-    {
      /*
-      * Add previous message as needed...
+      * Find start of value...
       */
 
-      if (m)
-        cupsArrayAdd(a, m);
+      if ((ptr = strchr(s, '\"')) == NULL)
+       continue;
+
+      ptr ++;
 
      /*
-      * Create a new message with the given msgid string...
+      * Unquote the text...
       */
 
-      if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
-      {
-        cupsFileClose(fp);
-       return (a);
-      }
+      if (flags & _CUPS_MESSAGE_UNQUOTE)
+       cups_unquote(ptr, ptr);
 
-      m->id = strdup(ptr);
-    }
-    else if (s[0] == '\"' && m)
-    {
      /*
-      * Append to current string...
+      * Create or add to a message...
       */
 
-      length = strlen(m->str ? m->str : m->id);
-
-      if ((temp = realloc(m->str ? m->str : m->id,
-                          length + strlen(ptr) + 1)) == NULL)
-      {
-       cupsFileClose(fp);
-       return (a);
-      }
-
-      if (m->str)
+      if (!strncmp(s, "msgid", 5))
       {
        /*
-        * Copy the new portion to the end of the msgstr string - safe
-       * to use strcpy because the buffer is allocated to the correct
-       * size...
+       * Add previous message as needed...
        */
 
-        m->str = temp;
-
-       strcpy(m->str + length, ptr);
-      }
-      else
+       if (m)
+       {
+         if (m->str && m->str[0])
+         {
+           cupsArrayAdd(a, m);
+         }
+         else
+         {
+          /*
+           * Translation is empty, don't add it... (STR #4033)
+           */
+
+           free(m->msg);
+           if (m->str)
+             free(m->str);
+           free(m);
+         }
+       }
+
+       /*
+       * Create a new message with the given msgid string...
+       */
+
+       if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
+         break;
+
+       if ((m->msg = strdup(ptr)) == NULL)
+       {
+         free(m);
+         m = NULL;
+         break;
+       }
+      }
+      else if (s[0] == '\"' && m)
       {
        /*
-        * Copy the new portion to the end of the msgid string - safe
-       * to use strcpy because the buffer is allocated to the correct
-       * size...
+       * Append to current string...
        */
 
-        m->id = temp;
+       length = strlen(m->str ? m->str : m->msg);
+       ptrlen = strlen(ptr);
+
+       if ((temp = realloc(m->str ? m->str : m->msg, length + ptrlen + 1)) == NULL)
+       {
+         if (m->str)
+           free(m->str);
+         free(m->msg);
+         free(m);
+         m = NULL;
+         break;
+       }
+
+       if (m->str)
+       {
+        /*
+         * Copy the new portion to the end of the msgstr string - safe
+         * to use memcpy because the buffer is allocated to the correct
+         * size...
+         */
+
+         m->str = temp;
+
+         memcpy(m->str + length, ptr, ptrlen + 1);
+       }
+       else
+       {
+        /*
+         * Copy the new portion to the end of the msgid string - safe
+         * to use memcpy because the buffer is allocated to the correct
+         * size...
+         */
+
+         m->msg = temp;
+
+         memcpy(m->msg + length, ptr, ptrlen + 1);
+       }
+      }
+      else if (!strncmp(s, "msgstr", 6) && m)
+      {
+       /*
+       * Set the string...
+       */
 
-       strcpy(m->id + length, ptr);
+       if ((m->str = strdup(ptr)) == NULL)
+       {
+         free(m->msg);
+         free(m);
+         m = NULL;
+          break;
+       }
       }
     }
-    else if (!strncmp(s, "msgstr", 6) && m)
+
+   /*
+    * Add the last message string to the array as needed...
+    */
+
+    if (m)
     {
-     /*
-      * Set the string...
-      */
+      if (m->str && m->str[0])
+      {
+       cupsArrayAdd(a, m);
+      }
+      else
+      {
+       /*
+       * Translation is empty, don't add it... (STR #4033)
+       */
 
-      m->str = strdup(ptr);
+       free(m->msg);
+       if (m->str)
+         free(m->str);
+       free(m);
+      }
     }
   }
 
- /*
-  * Add the last message string to the array as needed...
-  */
-
-  if (m)
-    cupsArrayAdd(a, m);
-
  /*
   * Close the message catalog file and return the new array...
   */
 
   cupsFileClose(fp);
 
+  DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...", cupsArrayCount(a)));
+
   return (a);
 }
 
@@ -944,13 +1155,55 @@ _cupsMessageLookup(cups_array_t *a,      /* I - Message array */
                        *match;         /* Matching message */
 
 
+  DEBUG_printf(("_cupsMessageLookup(a=%p, m=\"%s\")", (void *)a, m));
+
  /*
   * Lookup the message string; if it doesn't exist in the catalog,
   * then return the message that was passed to us...
   */
 
-  key.id = (char *)m;
-  match  = (_cups_message_t *)cupsArrayFind(a, &key);
+  key.msg = (char *)m;
+  match   = (_cups_message_t *)cupsArrayFind(a, &key);
+
+#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
+  if (!match && cupsArrayUserData(a))
+  {
+   /*
+    * Try looking the string up in the cups.strings dictionary...
+    */
+
+    CFDictionaryRef    dict;           /* cups.strings dictionary */
+    CFStringRef                cfm,            /* Message as a CF string */
+                       cfstr;          /* Localized text as a CF string */
+
+    dict       = (CFDictionaryRef)cupsArrayUserData(a);
+    cfm        = CFStringCreateWithCString(kCFAllocatorDefault, m, kCFStringEncodingUTF8);
+    match      = calloc(1, sizeof(_cups_message_t));
+    match->msg = strdup(m);
+    cfstr      = cfm ? CFDictionaryGetValue(dict, cfm) : NULL;
+
+    if (cfstr)
+    {
+      char     buffer[1024];           /* Message buffer */
+
+      CFStringGetCString(cfstr, buffer, sizeof(buffer), kCFStringEncodingUTF8);
+      match->str = strdup(buffer);
+
+      DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...", m, buffer));
+    }
+    else
+    {
+      match->str = strdup(m);
+
+      DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m));
+    }
+
+    cupsArrayAdd(a, match);
+
+    if (cfm)
+      CFRelease(cfm);
+  }
+#endif /* __APPLE__ && CUPS_BUNDLEDIR */
 
   if (match && match->str)
     return (match->str);
@@ -959,15 +1212,72 @@ _cupsMessageLookup(cups_array_t *a,      /* I - Message array */
 }
 
 
-#ifdef __APPLE__
 /*
- * Code & data to translate OSX's language names to their ISO 639-1 locale.
- *
- * The first version uses the new CoreFoundation API added in 10.3 (Panther),
- * the second is for 10.2 (Jaguar).
+ * '_cupsMessageNew()' - Make a new message catalog array.
  */
 
-#  ifdef HAVE_CF_LOCALE_ID
+cups_array_t *                         /* O - Array */
+_cupsMessageNew(void *context)         /* I - User data */
+{
+  return (cupsArrayNew3((cups_array_func_t)cups_message_compare, context,
+                        (cups_ahash_func_t)NULL, 0,
+                       (cups_acopy_func_t)NULL,
+                       (cups_afree_func_t)cups_message_free));
+}
+
+
+/*
+ * '_cupsMessageSave()' - Save a message catalog array.
+ */
+
+int                                    /* O - 0 on success, -1 on failure */
+_cupsMessageSave(const char   *filename,/* I - Output filename */
+                 int          flags,   /* I - Format flags */
+                 cups_array_t *a)      /* I - Message array */
+{
+  cups_file_t          *fp;            /* Output file */
+  _cups_message_t      *m;             /* Current message */
+
+
+ /*
+  * Output message catalog file...
+  */
+
+  if ((fp = cupsFileOpen(filename, "w")) == NULL)
+    return (-1);
+
+ /*
+  * Write each message...
+  */
+
+  if (flags & _CUPS_MESSAGE_STRINGS)
+  {
+    for (m = (_cups_message_t *)cupsArrayFirst(a); m; m = (_cups_message_t *)cupsArrayNext(a))
+    {
+      cupsFilePuts(fp, "\"");
+      cups_message_puts(fp, m->msg);
+      cupsFilePuts(fp, "\" = \"");
+      cups_message_puts(fp, m->str);
+      cupsFilePuts(fp, "\";\n");
+    }
+  }
+  else
+  {
+    for (m = (_cups_message_t *)cupsArrayFirst(a); m; m = (_cups_message_t *)cupsArrayNext(a))
+    {
+      cupsFilePuts(fp, "msgid \"");
+      cups_message_puts(fp, m->msg);
+      cupsFilePuts(fp, "\"\nmsgstr \"");
+      cups_message_puts(fp, m->str);
+      cupsFilePuts(fp, "\"\n");
+    }
+  }
+
+  return (cupsFileClose(fp));
+}
+
+
+#ifdef __APPLE__
 /*
  * 'appleLangDefault()' - Get the default locale string.
  */
@@ -975,61 +1285,118 @@ _cupsMessageLookup(cups_array_t *a,     /* I - Message array */
 static const char *                    /* O - Locale string */
 appleLangDefault(void)
 {
-  CFPropertyListRef    localizationList;
+  CFBundleRef          bundle;         /* Main bundle (if any) */
+  CFArrayRef           bundleList;     /* List of localizations in bundle */
+  CFPropertyListRef    localizationList = NULL;
                                        /* List of localization data */
   CFStringRef          languageName;   /* Current name */
-  CFStringRef          localeName;     /* Canonical from of name */
+  char                 *lang;          /* LANG environment variable */
   _cups_globals_t      *cg = _cupsGlobals();
                                        /* Pointer to library globals */
 
 
+  DEBUG_puts("2appleLangDefault()");
+
  /*
   * Only do the lookup and translation the first time.
   */
 
   if (!cg->language[0])
   {
-    localizationList =
-        CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
-                                  kCFPreferencesCurrentApplication);
+    if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL)
+    {
+      DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang));
+      strlcpy(cg->language, lang, sizeof(cg->language));
+      return (cg->language);
+    }
+    else if ((bundle = CFBundleGetMainBundle()) != NULL &&
+             (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
+    {
+      CFURLRef resources = CFBundleCopyResourcesDirectoryURL(bundle);
+
+      DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
+
+      if (resources)
+      {
+        CFStringRef    cfpath = CFURLCopyPath(resources);
+       char            path[1024];
+
+        if (cfpath)
+       {
+        /*
+         * See if we have an Info.plist file in the bundle...
+         */
+
+         CFStringGetCString(cfpath, path, sizeof(path), kCFStringEncodingUTF8);
+         DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path));
+         strlcat(path, "Contents/Info.plist", sizeof(path));
+
+          if (!access(path, R_OK))
+           localizationList = CFBundleCopyPreferredLocalizationsFromArray(bundleList);
+         else
+           DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL...");
+
+         CFRelease(cfpath);
+       }
+
+       CFRelease(resources);
+      }
+      else
+        DEBUG_puts("3appleLangDefault: No resource URL.");
+
+      CFRelease(bundleList);
+    }
 
-    if (localizationList != NULL)
+    if (!localizationList)
     {
+      DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
+
+      localizationList =
+         CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
+                                   kCFPreferencesCurrentApplication);
+    }
+
+    if (localizationList)
+    {
+#ifdef DEBUG
+      if (CFGetTypeID(localizationList) == CFArrayGetTypeID())
+        DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.",
+                      (int)CFArrayGetCount(localizationList)));
+      else
+        DEBUG_puts("3appleLangDefault: Got localizationList but not an array.");
+#endif /* DEBUG */
+
       if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
          CFArrayGetCount(localizationList) > 0)
       {
-        languageName = CFArrayGetValueAtIndex(localizationList, 0);
-
-        if (languageName != NULL &&
-            CFGetTypeID(languageName) == CFStringGetTypeID())
-        {
-         localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(
-                          kCFAllocatorDefault, languageName);
+       languageName = CFArrayGetValueAtIndex(localizationList, 0);
 
-         if (localeName != NULL)
-         {
-           CFStringGetCString(localeName, cg->language, sizeof(cg->language),
-                              kCFStringEncodingASCII);
-           CFRelease(localeName);
-
-           if (!strcmp(cg->language, "en"))
-             strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
-           else if (strchr(cg->language, '.') == NULL)
-             strlcat(cg->language, ".UTF-8", sizeof(cg->language));
-         }
-        }
+       if (languageName &&
+           CFGetTypeID(languageName) == CFStringGetTypeID())
+       {
+         if (_cupsAppleLocale(languageName, cg->language, sizeof(cg->language)))
+           DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
+                         cg->language));
+         else
+           DEBUG_puts("3appleLangDefault: Unable to get locale.");
+       }
       }
 
       CFRelease(localizationList);
     }
-  
+
    /*
     * If we didn't find the language, default to en_US...
     */
 
     if (!cg->language[0])
+    {
+      DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
       strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
+    }
   }
+  else
+    DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg->language));
 
  /*
   * Return the cached locale...
@@ -1037,159 +1404,197 @@ appleLangDefault(void)
 
   return (cg->language);
 }
-#  else
-/*
- * Code & data to translate OSX 10.2's language names to their ISO 639-1
- * locale.
- */
-
-typedef struct
-{
-  const char * const name;             /* Language name */
-  const char * const locale;           /* Locale name */
-} _apple_name_locale_t;
-
-static const _apple_name_locale_t apple_name_locale[] =
-{
-  { "English"     , "en_US.UTF-8" },   { "French"     , "fr.UTF-8" },
-  { "German"      , "de.UTF-8" },      { "Italian"    , "it.UTF-8" },  
-  { "Dutch"       , "nl.UTF-8" },      { "Swedish"    , "sv.UTF-8" },
-  { "Spanish"     , "es.UTF-8" },      { "Danish"     , "da.UTF-8" },  
-  { "Portuguese"  , "pt.UTF-8" },      { "Norwegian"  , "no.UTF-8" },
-  { "Hebrew"      , "he.UTF-8" },      { "Japanese"   , "ja.UTF-8" },  
-  { "Arabic"      , "ar.UTF-8" },      { "Finnish"    , "fi.UTF-8" },
-  { "Greek"       , "el.UTF-8" },      { "Icelandic"  , "is.UTF-8" },  
-  { "Maltese"     , "mt.UTF-8" },      { "Turkish"    , "tr.UTF-8" },
-  { "Croatian"    , "hr.UTF-8" },      { "Chinese"    , "zh.UTF-8" },  
-  { "Urdu"        , "ur.UTF-8" },      { "Hindi"      , "hi.UTF-8" },
-  { "Thai"        , "th.UTF-8" },      { "Korean"     , "ko.UTF-8" },  
-  { "Lithuanian"  , "lt.UTF-8" },      { "Polish"     , "pl.UTF-8" },
-  { "Hungarian"   , "hu.UTF-8" },      { "Estonian"   , "et.UTF-8" },  
-  { "Latvian"     , "lv.UTF-8" },      { "Sami"       , "se.UTF-8" },
-  { "Faroese"     , "fo.UTF-8" },      { "Farsi"      , "fa.UTF-8" },  
-  { "Russian"     , "ru.UTF-8" },      { "Chinese"    , "zh.UTF-8" },
-  { "Dutch"       , "nl.UTF-8" },      { "Irish"      , "ga.UTF-8" },  
-  { "Albanian"    , "sq.UTF-8" },      { "Romanian"   , "ro.UTF-8" },
-  { "Czech"       , "cs.UTF-8" },      { "Slovak"     , "sk.UTF-8" },  
-  { "Slovenian"   , "sl.UTF-8" },      { "Yiddish"    , "yi.UTF-8" },
-  { "Serbian"     , "sr.UTF-8" },      { "Macedonian" , "mk.UTF-8" },  
-  { "Bulgarian"   , "bg.UTF-8" },      { "Ukrainian"  , "uk.UTF-8" },
-  { "Byelorussian", "be.UTF-8" },      { "Uzbek"      , "uz.UTF-8" },  
-  { "Kazakh"      , "kk.UTF-8" },      { "Azerbaijani", "az.UTF-8" },
-  { "Azerbaijani" , "az.UTF-8" },      { "Armenian"   , "hy.UTF-8" },  
-  { "Georgian"    , "ka.UTF-8" },      { "Moldavian"  , "mo.UTF-8" },
-  { "Kirghiz"     , "ky.UTF-8" },      { "Tajiki"     , "tg.UTF-8" },  
-  { "Turkmen"     , "tk.UTF-8" },      { "Mongolian"  , "mn.UTF-8" },
-  { "Mongolian"   , "mn.UTF-8" },      { "Pashto"     , "ps.UTF-8" },  
-  { "Kurdish"     , "ku.UTF-8" },      { "Kashmiri"   , "ks.UTF-8" },
-  { "Sindhi"      , "sd.UTF-8" },      { "Tibetan"    , "bo.UTF-8" },  
-  { "Nepali"      , "ne.UTF-8" },      { "Sanskrit"   , "sa.UTF-8" },
-  { "Marathi"     , "mr.UTF-8" },      { "Bengali"    , "bn.UTF-8" },  
-  { "Assamese"    , "as.UTF-8" },      { "Gujarati"   , "gu.UTF-8" },
-  { "Punjabi"     , "pa.UTF-8" },      { "Oriya"      , "or.UTF-8" },  
-  { "Malayalam"   , "ml.UTF-8" },      { "Kannada"    , "kn.UTF-8" },
-  { "Tamil"       , "ta.UTF-8" },      { "Telugu"     , "te.UTF-8" },  
-  { "Sinhalese"   , "si.UTF-8" },      { "Burmese"    , "my.UTF-8" },
-  { "Khmer"       , "km.UTF-8" },      { "Lao"        , "lo.UTF-8" },  
-  { "Vietnamese"  , "vi.UTF-8" },      { "Indonesian" , "id.UTF-8" },
-  { "Tagalog"     , "tl.UTF-8" },      { "Malay"      , "ms.UTF-8" },  
-  { "Malay"       , "ms.UTF-8" },      { "Amharic"    , "am.UTF-8" },
-  { "Tigrinya"    , "ti.UTF-8" },      { "Oromo"      , "om.UTF-8" },  
-  { "Somali"      , "so.UTF-8" },      { "Swahili"    , "sw.UTF-8" },
-  { "Kinyarwanda" , "rw.UTF-8" },      { "Rundi"      , "rn.UTF-8" },  
-  { "Nyanja"      , "" },              { "Malagasy"   , "mg.UTF-8" },
-  { "Esperanto"   , "eo.UTF-8" },      { "Welsh"      , "cy.UTF-8" },  
-  { "Basque"      , "eu.UTF-8" },      { "Catalan"    , "ca.UTF-8" },
-  { "Latin"       , "la.UTF-8" },      { "Quechua"    , "qu.UTF-8" },  
-  { "Guarani"     , "gn.UTF-8" },      { "Aymara"     , "ay.UTF-8" },
-  { "Tatar"       , "tt.UTF-8" },      { "Uighur"     , "ug.UTF-8" },  
-  { "Dzongkha"    , "dz.UTF-8" },      { "Javanese"   , "jv.UTF-8" },
-  { "Sundanese"   , "su.UTF-8" },      { "Galician"   , "gl.UTF-8" },  
-  { "Afrikaans"   , "af.UTF-8" },      { "Breton"     , "br.UTF-8" },
-  { "Inuktitut"   , "iu.UTF-8" },      { "Scottish"   , "gd.UTF-8" },  
-  { "Manx"        , "gv.UTF-8" },      { "Irish"      , "ga.UTF-8" },
-  { "Tongan"      , "to.UTF-8" },      { "Greek"      , "el.UTF-8" },  
-  { "Greenlandic" , "kl.UTF-8" },      { "Azerbaijani", "az.UTF-8" }
-};
 
 
+#  ifdef CUPS_BUNDLEDIR
 /*
- * 'appleLangDefault()' - Get the default locale string.
+ * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
  */
 
-static const char *                    /* O - Locale string */
-appleLangDefault(void)
+static cups_array_t *                  /* O - Message catalog */
+appleMessageLoad(const char *locale)   /* I - Locale ID */
 {
-  int                  i;              /* Looping var */
-  CFPropertyListRef    localizationList;
-                                       /* List of localization data */
-  CFStringRef          localizationName;
-                                       /* Current name */
-  char                 buff[256];      /* Temporary buffer */
-  _cups_globals_t      *cg = _cupsGlobals();
-                                       /* Pointer to library globals */
+  char                 filename[1024], /* Path to cups.strings file */
+                       applelang[256], /* Apple language ID */
+                       baselang[4];    /* Base language */
+  CFURLRef             url;            /* URL to cups.strings file */
+  CFReadStreamRef      stream = NULL;  /* File stream */
+  CFPropertyListRef    plist = NULL;   /* Localization file */
+#ifdef DEBUG
+  const char            *cups_strings = getenv("CUPS_STRINGS");
+                                        /* Test strings file */
+  CFErrorRef           error = NULL;   /* Error when opening file */
+#endif /* DEBUG */
+
 
+  DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale));
 
  /*
-  * Only do the lookup and translation the first time.
+  * Load the cups.strings file...
   */
 
-  if (cg->language == NULL)
+#ifdef DEBUG
+  if (cups_strings)
   {
-    localizationList =
-        CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
-                                  kCFPreferencesCurrentApplication);
+    DEBUG_puts("1appleMessageLoad: Using debug CUPS_STRINGS file.");
+    strlcpy(filename, cups_strings, sizeof(filename));
+  }
+  else
+#endif /* DEBUG */
+
+  snprintf(filename, sizeof(filename),
+           CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings",
+          _cupsAppleLanguage(locale, applelang, sizeof(applelang)));
+
+  if (access(filename, 0))
+  {
+   /*
+    * <rdar://problem/22086642>
+    *
+    * Try with original locale string...
+    */
+
+    DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
+    snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
+  }
+
+  if (access(filename, 0))
+  {
+   /*
+    * <rdar://problem/25292403>
+    *
+    * Try with just the language code...
+    */
+
+    DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
+
+    strlcpy(baselang, locale, sizeof(baselang));
+    if (baselang[3] == '-' || baselang[3] == '_')
+      baselang[3] = '\0';
+
+    snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", baselang);
+  }
 
-    if (localizationList != NULL)
+  if (access(filename, 0))
+  {
+   /*
+    * Try alternate lproj directory names...
+    */
+
+    DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
+
+    if (!strncmp(locale, "en", 2))
+      locale = "English";
+    else if (!strncmp(locale, "nb", 2))
+      locale = "no";
+    else if (!strncmp(locale, "nl", 2))
+      locale = "Dutch";
+    else if (!strncmp(locale, "fr", 2))
+      locale = "French";
+    else if (!strncmp(locale, "de", 2))
+      locale = "German";
+    else if (!strncmp(locale, "it", 2))
+      locale = "Italian";
+    else if (!strncmp(locale, "ja", 2))
+      locale = "Japanese";
+    else if (!strncmp(locale, "es", 2))
+      locale = "Spanish";
+    else if (!strcmp(locale, "zh_HK") || !strncasecmp(locale, "zh-Hant", 7) || !strncasecmp(locale, "zh_Hant", 7))
     {
-      if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
-         CFArrayGetCount(localizationList) > 0)
+     /*
+      * <rdar://problem/22130168>
+      * <rdar://problem/27245567>
+      *
+      * Try zh_TW first, then zh...  Sigh...
+      */
+
+      if (!access(CUPS_BUNDLEDIR "/Resources/zh_TW.lproj/cups.strings", 0))
+        locale = "zh_TW";
+      else
+        locale = "zh";
+    }
+    else if (strstr(locale, "_") != NULL || strstr(locale, "-") != NULL)
+    {
+     /*
+      * Drop country code, just try language...
+      */
+
+      strlcpy(baselang, locale, sizeof(baselang));
+      if (baselang[2] == '-' || baselang[2] == '_')
+        baselang[2] = '\0';
+
+      locale = baselang;
+    }
+
+    snprintf(filename, sizeof(filename),
+            CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
+  }
+
+  DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename));
+
+  url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
+                                                (UInt8 *)filename,
+                                               (CFIndex)strlen(filename), false);
+  if (url)
+  {
+    stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
+    if (stream)
+    {
+     /*
+      * Read the property list containing the localization data.
+      *
+      * NOTE: This code currently generates a clang "potential leak"
+      * warning, but the object is released in _cupsMessageFree().
+      */
+
+      CFReadStreamOpen(stream);
+
+#ifdef DEBUG
+      plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0,
+                                             kCFPropertyListImmutable, NULL,
+                                             &error);
+      if (error)
       {
-       localizationName = CFArrayGetValueAtIndex(localizationList, 0);
+        CFStringRef    msg = CFErrorCopyDescription(error);
+                                       /* Error message */
 
-       if (localizationName != NULL &&
-            CFGetTypeID(localizationName) == CFStringGetTypeID())
-       {
-         CFIndex length = CFStringGetLength(localizationName);
+        CFStringGetCString(msg, filename, sizeof(filename),
+                           kCFStringEncodingUTF8);
+        DEBUG_printf(("1appleMessageLoad: %s", filename));
 
-         if (length <= sizeof(buff) &&
-             CFStringGetCString(localizationName, buff, sizeof(buff),
-                                kCFStringEncodingASCII))
-         {
-           buff[sizeof(buff) - 1] = '\0';
-
-           for (i = 0;
-                i < sizeof(apple_name_locale) / sizeof(apple_name_locale[0]);
-                i++)
-           {
-             if (!strcasecmp(buff, apple_name_locale[i].name))
-             {
-               cg->language = apple_name_locale[i].locale;
-               break;
-             }
-           }
-         }
-       }
+       CFRelease(msg);
+        CFRelease(error);
       }
 
-      CFRelease(localizationList);
+#else
+      plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0,
+                                             kCFPropertyListImmutable, NULL,
+                                             NULL);
+#endif /* DEBUG */
+
+      if (plist && CFGetTypeID(plist) != CFDictionaryGetTypeID())
+      {
+         CFRelease(plist);
+         plist = NULL;
+      }
+
+      CFRelease(stream);
     }
-  
-   /*
-    * If we didn't find the language, default to en_US...
-    */
 
-    if (cg->language == NULL)
-      cg->language = apple_name_locale[0].locale;
+    CFRelease(url);
   }
 
+  DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url, stream,
+                plist));
+
  /*
-  * Return the cached locale...
+  * Create and return an empty array to act as a cache for messages, passing the
+  * plist as the user data.
   */
 
-  return (cg->language);
+  return (_cupsMessageNew((void *)plist));
 }
-#  endif /* HAVE_CF_LOCALE_ID */
+#  endif /* CUPS_BUNDLEDIR */
 #endif /* __APPLE__ */
 
 
@@ -1198,14 +1603,14 @@ appleLangDefault(void)
  */
 
 static cups_lang_t *                   /* O - Language data or NULL */
-cups_cache_lookup(const char      *name,/* I - Name of locale */
-                  cups_encoding_t encoding)
-                                       /* I - Encoding of locale */
+cups_cache_lookup(
+    const char      *name,             /* I - Name of locale */
+    cups_encoding_t encoding)          /* I - Encoding of locale */
 {
   cups_lang_t  *lang;                  /* Current language */
 
 
-  DEBUG_printf(("cups_cache_lookup(name=\"%s\", encoding=%d(%s))\n", name,
+  DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name,
                 encoding, encoding == CUPS_AUTO_ENCODING ? "auto" :
                              lang_encodings[encoding]));
 
@@ -1215,8 +1620,8 @@ cups_cache_lookup(const char      *name,/* I - Name of locale */
 
   for (lang = lang_cache; lang != NULL; lang = lang->next)
   {
-    DEBUG_printf(("cups_cache_lookup: lang=%p, language=\"%s\", encoding=%d(%s)\n",
-                  lang, lang->language, lang->encoding,
+    DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
+                 "encoding=%d(%s)", (void *)lang, lang->language, lang->encoding,
                  lang_encodings[lang->encoding]));
 
     if (!strcmp(lang->language, name) &&
@@ -1224,13 +1629,13 @@ cups_cache_lookup(const char      *name,/* I - Name of locale */
     {
       lang->used ++;
 
-      DEBUG_puts("cups_cache_lookup: returning match!");
+      DEBUG_puts("8cups_cache_lookup: returning match!");
 
       return (lang);
     }
   }
 
-  DEBUG_puts("cups_cache_lookup: returning NULL!");
+  DEBUG_puts("8cups_cache_lookup: returning NULL!");
 
   return (NULL);
 }
@@ -1245,7 +1650,229 @@ cups_message_compare(
     _cups_message_t *m1,               /* I - First message */
     _cups_message_t *m2)               /* I - Second message */
 {
-  return (strcmp(m1->id, m2->id));
+  return (strcmp(m1->msg, m2->msg));
+}
+
+
+/*
+ * 'cups_message_free()' - Free a message.
+ */
+
+static void
+cups_message_free(_cups_message_t *m)  /* I - Message */
+{
+  if (m->msg)
+    free(m->msg);
+
+  if (m->str)
+    free(m->str);
+
+  free(m);
+}
+
+
+/*
+ * 'cups_message_load()' - Load the message catalog for a language.
+ */
+
+static void
+cups_message_load(cups_lang_t *lang)   /* I - Language */
+{
+#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
+  lang->strings = appleMessageLoad(lang->language);
+
+#else
+  char                 filename[1024]; /* Filename for language locale file */
+  _cups_globals_t      *cg = _cupsGlobals();
+                                       /* Pointer to library globals */
+
+
+  snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
+          lang->language, lang->language);
+
+  if (strchr(lang->language, '_') && access(filename, 0))
+  {
+   /*
+    * Country localization not available, look for generic localization...
+    */
+
+    snprintf(filename, sizeof(filename), "%s/%.2s/cups_%.2s.po", cg->localedir,
+             lang->language, lang->language);
+
+    if (access(filename, 0))
+    {
+     /*
+      * No generic localization, so use POSIX...
+      */
+
+      DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename,
+                    strerror(errno)));
+
+      snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir);
+    }
+  }
+
+ /*
+  * Read the strings from the file...
+  */
+
+  lang->strings = _cupsMessageLoad(filename, _CUPS_MESSAGE_UNQUOTE);
+#endif /* __APPLE__ && CUPS_BUNDLEDIR */
+}
+
+
+/*
+ * 'cups_message_puts()' - Write a message string with quoting.
+ */
+
+static void
+cups_message_puts(cups_file_t *fp,     /* I - File to write to */
+                  const char  *s)      /* I - String to write */
+{
+  const char   *start,                 /* Start of substring */
+               *ptr;                   /* Pointer into string */
+
+
+  for (start = s, ptr = s; *ptr; ptr ++)
+  {
+    if (strchr("\\\"\n\t", *ptr))
+    {
+      if (ptr > start)
+      {
+       cupsFileWrite(fp, start, (size_t)(ptr - start));
+       start = ptr + 1;
+      }
+
+      if (*ptr == '\\')
+        cupsFileWrite(fp, "\\\\", 2);
+      else if (*ptr == '\"')
+        cupsFileWrite(fp, "\\\"", 2);
+      else if (*ptr == '\n')
+        cupsFileWrite(fp, "\\n", 2);
+      else /* if (*ptr == '\t') */
+        cupsFileWrite(fp, "\\t", 2);
+    }
+  }
+
+  if (ptr > start)
+    cupsFileWrite(fp, start, (size_t)(ptr - start));
+}
+
+
+/*
+ * 'cups_read_strings()' - Read a pair of strings from a .strings file.
+ */
+
+static int                             /* O - 1 on success, 0 on failure */
+cups_read_strings(cups_file_t  *fp,    /* I - .strings file */
+                  int          flags,  /* I - CUPS_MESSAGE_xxx flags */
+                 cups_array_t *a)      /* I - Message catalog array */
+{
+  char                 buffer[8192],   /* Line buffer */
+                       *bufptr,        /* Pointer into buffer */
+                       *msg,           /* Pointer to start of message */
+                       *str;           /* Pointer to start of translation string */
+  _cups_message_t      *m;             /* New message */
+
+
+  while (cupsFileGets(fp, buffer, sizeof(buffer)))
+  {
+   /*
+    * Skip any line (comments, blanks, etc.) that isn't:
+    *
+    *   "message" = "translation";
+    */
+
+    for (bufptr = buffer; *bufptr && isspace(*bufptr & 255); bufptr ++);
+
+    if (*bufptr != '\"')
+      continue;
+
+   /*
+    * Find the end of the message...
+    */
+
+    bufptr ++;
+    for (msg = bufptr; *bufptr && *bufptr != '\"'; bufptr ++)
+      if (*bufptr == '\\' && bufptr[1])
+        bufptr ++;
+
+    if (!*bufptr)
+      continue;
+
+    *bufptr++ = '\0';
+
+    if (flags & _CUPS_MESSAGE_UNQUOTE)
+      cups_unquote(msg, msg);
+
+   /*
+    * Find the start of the translation...
+    */
+
+    while (*bufptr && isspace(*bufptr & 255))
+      bufptr ++;
+
+    if (*bufptr != '=')
+      continue;
+
+    bufptr ++;
+    while (*bufptr && isspace(*bufptr & 255))
+      bufptr ++;
+
+    if (*bufptr != '\"')
+      continue;
+
+   /*
+    * Find the end of the translation...
+    */
+
+    bufptr ++;
+    for (str = bufptr; *bufptr && *bufptr != '\"'; bufptr ++)
+      if (*bufptr == '\\' && bufptr[1])
+        bufptr ++;
+
+    if (!*bufptr)
+      continue;
+
+    *bufptr++ = '\0';
+
+    if (flags & _CUPS_MESSAGE_UNQUOTE)
+      cups_unquote(str, str);
+
+   /*
+    * If we get this far we have a valid pair of strings, add them...
+    */
+
+    if ((m = malloc(sizeof(_cups_message_t))) == NULL)
+      break;
+
+    m->msg = strdup(msg);
+    m->str = strdup(str);
+
+    if (m->msg && m->str)
+    {
+      cupsArrayAdd(a, m);
+    }
+    else
+    {
+      if (m->msg)
+       free(m->msg);
+
+      if (m->str)
+       free(m->str);
+
+      free(m);
+      break;
+    }
+
+    return (1);
+  }
+
+ /*
+  * No more strings...
+  */
+
+  return (0);
 }
 
 
@@ -1271,6 +1898,8 @@ cups_unquote(char       *d,               /* O - Unquoted string */
          *d = *d * 8 + *s - '0';
          s ++;
        }
+
+       d ++;
       }
       else
       {
@@ -1292,8 +1921,3 @@ cups_unquote(char       *d,               /* O - Unquoted string */
 
   *d = '\0';
 }
-
-
-/*
- * End of "$Id$".
- */