]> 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 aa167ad24d7a544d717739ef43576b1b362103a6..7c848c09cf5f62398c8d83969a316be164aa8af1 100644 (file)
@@ -1,18 +1,10 @@
 /*
- * "$Id$"
- *
  * I18N/language support for CUPS.
  *
- * Copyright 2007-2015 by Apple Inc.
+ * Copyright 2007-2017 by Apple Inc.
  * Copyright 1997-2007 by Easy Software Products.
  *
- * These coded instructions, statements, and computer programs are the
- * property of Apple Inc. 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
- * file is missing or damaged, see the license at "http://www.cups.org/".
- *
- * This file is subject to the Apple OS-Developed Software exception.
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
  */
 
 /*
 #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 */
@@ -120,11 +112,15 @@ typedef struct
 } _apple_language_locale_t;
 
 static const _apple_language_locale_t apple_language_locale[] =
-{                                      /* Locale to language ID LUT */
-  { "en",      "en_US" },
-  { "nb",      "no" },
-  { "zh-Hans", "zh_CN" },
-  { "zh-Hant", "zh_TW" }
+{                                      /* 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__ */
 
@@ -144,16 +140,15 @@ static const char *appleLangDefault(void);
 #        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;
+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 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);
 
 
@@ -241,6 +236,96 @@ _cupsAppleLanguage(const char *locale,     /* I - Locale ID */
 
   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__ */
 
 
@@ -600,6 +685,15 @@ cupsLangGet(const char *language)  /* I - Language or locale */
           *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])
@@ -619,7 +713,7 @@ 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)
     {
       strlcpy(langname, "C", sizeof(langname));
       country[0] = '\0';
@@ -759,6 +853,9 @@ _cupsLangString(cups_lang_t *lang,  /* I - Language */
 {
   const char *s;                       /* Localized message */
 
+
+  DEBUG_printf(("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang, message));
+
  /*
   * Range check input...
   */
@@ -808,12 +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 */
-                 int        unquote)   /* I - Unescape \foo in strings? */
+                 int        flags)     /* I - Load flags */
 {
   cups_file_t          *fp;            /* Message file */
   cups_array_t         *a;             /* Message array */
@@ -848,187 +945,189 @@ _cupsMessageLoad(const char *filename,  /* I - Message catalog to load */
     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';
-
-   /*
-    * Find start of value...
-    */
+    m = NULL;
 
-    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...
+      */
 
-    if (unquote)
-      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)
-      {
-        if (m->str && m->str[0])
-        {
-          cupsArrayAdd(a, m);
-        }
-        else
-        {
-         /*
-          * Translation is empty, don't add it... (STR #4033)
-          */
-
-          free(m->id);
-          if (m->str)
-            free(m->str);
-          free(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);
 
-      if ((m->id = strdup(ptr)) == NULL)
-      {
-        free(m);
-        cupsFileClose(fp);
-       return (a);
-      }
-    }
-    else if (s[0] == '\"' && m)
-    {
      /*
-      * Append to current string...
+      * Create or add to a message...
       */
 
-      length = strlen(m->str ? m->str : m->id);
-      ptrlen = strlen(ptr);
-
-      if ((temp = realloc(m->str ? m->str : m->id, length + ptrlen + 1)) == NULL)
+      if (!strncmp(s, "msgid", 5))
       {
-        if (m->str)
-         free(m->str);
-       free(m->id);
-        free(m);
+       /*
+       * Add previous message as needed...
+       */
 
-       cupsFileClose(fp);
-       return (a);
-      }
+       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);
+         }
+       }
 
-      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...
+       * Create a new message with the given msgid string...
        */
 
-        m->str = temp;
+       if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
+         break;
 
-       memcpy(m->str + length, ptr, ptrlen + 1);
+       if ((m->msg = strdup(ptr)) == NULL)
+       {
+         free(m);
+         m = NULL;
+         break;
+       }
       }
-      else
+      else if (s[0] == '\"' && m)
       {
        /*
-        * Copy the new portion to the end of the msgid string - safe
-       * to use memcpy 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);
 
-       memcpy(m->id + length, ptr, ptrlen + 1);
-      }
-    }
-    else if (!strncmp(s, "msgstr", 6) && m)
-    {
-     /*
-      * Set the string...
-      */
+       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;
 
-      if ((m->str = strdup(ptr)) == NULL)
+         memcpy(m->msg + length, ptr, ptrlen + 1);
+       }
+      }
+      else if (!strncmp(s, "msgstr", 6) && m)
       {
-       free(m->id);
-        free(m);
+       /*
+       * Set the string...
+       */
 
-        cupsFileClose(fp);
-       return (a);
+       if ((m->str = strdup(ptr)) == NULL)
+       {
+         free(m->msg);
+         free(m);
+         m = NULL;
+          break;
+       }
       }
     }
-  }
 
- /*
-  * Add the last message string to the array as needed...
-  */
  /*
+    * Add the last message string to the array as needed...
+    */
 
-  if (m)
-  {
-    if (m->str && m->str[0])
-    {
-      cupsArrayAdd(a, m);
-    }
-    else
+    if (m)
     {
-     /*
-      * Translation is empty, don't add it... (STR #4033)
-      */
+      if (m->str && m->str[0])
+      {
+       cupsArrayAdd(a, m);
+      }
+      else
+      {
+       /*
+       * Translation is empty, don't add it... (STR #4033)
+       */
 
-      free(m->id);
-      if (m->str)
-       free(m->str);
-      free(m);
+       free(m->msg);
+       if (m->str)
+         free(m->str);
+       free(m);
+      }
     }
   }
 
@@ -1038,8 +1137,7 @@ _cupsMessageLoad(const char *filename,    /* I - Message catalog to load */
 
   cupsFileClose(fp);
 
-  DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
-                cupsArrayCount(a)));
+  DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...", cupsArrayCount(a)));
 
   return (a);
 }
@@ -1057,13 +1155,15 @@ _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))
@@ -1076,12 +1176,11 @@ _cupsMessageLookup(cups_array_t *a,     /* I - Message array */
     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->id = strdup(m);
-    cfstr     = cfm ? CFDictionaryGetValue(dict, cfm) : NULL;
+    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)
     {
@@ -1090,8 +1189,7 @@ _cupsMessageLookup(cups_array_t *a,       /* I - Message array */
       CFStringGetCString(cfstr, buffer, sizeof(buffer), kCFStringEncodingUTF8);
       match->str = strdup(buffer);
 
-      DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...",
-                    m, buffer));
+      DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...", m, buffer));
     }
     else
     {
@@ -1128,6 +1226,57 @@ _cupsMessageNew(void *context)           /* I - User data */
 }
 
 
+/*
+ * '_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.
@@ -1136,13 +1285,11 @@ _cupsMessageNew(void *context)          /* I - User data */
 static const char *                    /* O - Locale string */
 appleLangDefault(void)
 {
-  int                  i;              /* Looping var */
   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 */
@@ -1227,49 +1374,11 @@ appleLangDefault(void)
        if (languageName &&
            CFGetTypeID(languageName) == CFStringGetTypeID())
        {
-         localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(
-                          kCFAllocatorDefault, languageName);
-
-         if (localeName)
-         {
-           CFStringGetCString(localeName, cg->language, sizeof(cg->language),
-                              kCFStringEncodingASCII);
-           CFRelease(localeName);
-
+         if (_cupsAppleLocale(languageName, cg->language, sizeof(cg->language)))
            DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
                          cg->language));
-
-          /*
-           * Map new language identifiers to locales...
-           */
-
-           for (i = 0;
-                i < (int)(sizeof(apple_language_locale) /
-                          sizeof(apple_language_locale[0]));
-                i ++)
-           {
-             if (!strcmp(cg->language, apple_language_locale[i].language))
-             {
-               DEBUG_printf(("3appleLangDefault: mapping \"%s\" to \"%s\"...",
-                             cg->language, apple_language_locale[i].locale));
-               strlcpy(cg->language, apple_language_locale[i].locale,
-                       sizeof(cg->language));
-               break;
-             }
-           }
-
-          /*
-           * Convert language subtag into region subtag...
-           */
-
-           if (cg->language[2] == '-')
-             cg->language[2] = '_';
-
-           if (!strchr(cg->language, '.'))
-             strlcat(cg->language, ".UTF-8", sizeof(cg->language));
-         }
          else
-           DEBUG_puts("3appleLangDefault: Unable to get localeName.");
+           DEBUG_puts("3appleLangDefault: Unable to get locale.");
        }
       }
 
@@ -1307,11 +1416,13 @@ appleMessageLoad(const char *locale)    /* I - Locale ID */
 {
   char                 filename[1024], /* Path to cups.strings file */
                        applelang[256], /* Apple language ID */
-                       baselang[3];    /* Base language */
+                       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 */
 
@@ -1322,6 +1433,15 @@ appleMessageLoad(const char *locale)     /* I - Locale ID */
   * Load the cups.strings file...
   */
 
+#ifdef DEBUG
+  if (cups_strings)
+  {
+    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)));
@@ -1334,10 +1454,26 @@ appleMessageLoad(const char *locale)    /* I - Locale ID */
     * 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);
   }
 
-  DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename));
+  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 (access(filename, 0))
   {
@@ -1345,9 +1481,13 @@ appleMessageLoad(const char *locale)     /* I - Locale ID */
     * 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) || !strncmp(locale, "nl", 2))
+    else if (!strncmp(locale, "nb", 2))
+      locale = "no";
+    else if (!strncmp(locale, "nl", 2))
       locale = "Dutch";
     else if (!strncmp(locale, "fr", 2))
       locale = "French";
@@ -1359,10 +1499,11 @@ appleMessageLoad(const char *locale)    /* I - Locale ID */
       locale = "Japanese";
     else if (!strncmp(locale, "es", 2))
       locale = "Spanish";
-    else if (!strcmp(locale, "zh_HK"))
+    else if (!strcmp(locale, "zh_HK") || !strncasecmp(locale, "zh-Hant", 7) || !strncasecmp(locale, "zh_Hant", 7))
     {
      /*
       * <rdar://problem/22130168>
+      * <rdar://problem/27245567>
       *
       * Try zh_TW first, then zh...  Sigh...
       */
@@ -1379,14 +1520,18 @@ appleMessageLoad(const char *locale)    /* I - Locale ID */
       */
 
       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: alternate filename=\"%s\"", filename));
   }
 
+  DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename));
+
   url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
                                                 (UInt8 *)filename,
                                                (CFIndex)strlen(filename), false);
@@ -1505,7 +1650,7 @@ 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));
 }
 
 
@@ -1516,8 +1661,8 @@ cups_message_compare(
 static void
 cups_message_free(_cups_message_t *m)  /* I - Message */
 {
-  if (m->id)
-    free(m->id);
+  if (m->msg)
+    free(m->msg);
 
   if (m->str)
     free(m->str);
@@ -1571,11 +1716,166 @@ cups_message_load(cups_lang_t *lang)   /* I - Language */
   * Read the strings from the file...
   */
 
-  lang->strings = _cupsMessageLoad(filename, 1);
+  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);
+}
+
+
 /*
  * 'cups_unquote()' - Unquote characters in strings...
  */
@@ -1621,8 +1921,3 @@ cups_unquote(char       *d,               /* O - Unquoted string */
 
   *d = '\0';
 }
-
-
-/*
- * End of "$Id$".
- */