]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - cups/localize.c
Merge changes from CUPS 1.5svn-r9641
[thirdparty/cups.git] / cups / localize.c
index bb9024f9ca50a1c30aee473d5773c35029e862bd..52518339ad7e7bf7db504585183597b39a082e7d 100644 (file)
@@ -1,25 +1,16 @@
 /*
- * "$Id: localize.c 5824 2006-08-15 18:19:45Z mike $"
+ * "$Id: localize.c 7679 2008-06-19 23:37:45Z mike $"
  *
- *   PPD custom option routines for the Common UNIX Printing System (CUPS).
+ *   PPD localization routines for CUPS.
  *
- *   Copyright 1997-2006 by Easy Software Products, all rights reserved.
+ *   Copyright 2007-2010 by Apple Inc.
+ *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
  *
  *   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
+ *   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/".
  *
  *   PostScript is a trademark of Adobe Systems, Inc.
  *
  *
  * Contents:
  *
- *   ppdLocalize() - Localize the PPD file to the current locale.
+ *   ppdLocalize()           - Localize the PPD file to the current locale.
+ *   ppdLocalizeAttr()       - Localize an attribute.
+ *   ppdLocalizeIPPReason()  - Get the localized version of a cupsIPPReason
+ *                             attribute.
+ *   ppdLocalizeMarkerName() - Get the localized version of a marker-names
+ *                             attribute value.
+ *   _ppdFreeLanguages()     - Free an array of languages from _ppdGetLanguages.
+ *   _ppdGetLanguages()      - Get an array of languages from a PPD file.
+ *   _ppdHashName()          - Generate a hash value for a device or profile
+ *                             name.
+ *   _ppdLocalizedAttr()     - Find a localized attribute.
+ *   ppd_ll_CC()             - Get the current locale names.
  */
 
 /*
  * Include necessary headers.
  */
 
-#include "globals.h"
-#include "debug.h"
+#include "cups-private.h"
+#include "ppd-private.h"
 
 
 /*
  * Local functions...
  */
 
-static const char      *ppd_text(ppd_file_t *ppd, const char *keyword,
-                                 const char *spec, const char *ll_CC,
-                                 const char *ll);
+static cups_lang_t     *ppd_ll_CC(char *ll_CC, int ll_CC_size);
 
 
 /*
  * 'ppdLocalize()' - Localize the PPD file to the current locale.
  *
- * @since CUPS 1.2@
+ * All groups, options, and choices are localized, as are ICC profile
+ * descriptions, printer presets, and custom option parameters.  Each
+ * localized string uses the UTF-8 character encoding.
+ *
+ * @since CUPS 1.2/Mac OS X 10.5@
  */
 
 int                                    /* O - 0 on success, -1 on error */
@@ -69,18 +73,17 @@ ppdLocalize(ppd_file_t *ppd)                /* I - PPD file */
   ppd_choice_t *choice;                /* Current choice */
   ppd_coption_t        *coption;               /* Current custom option */
   ppd_cparam_t *cparam;                /* Current custom parameter */
-  cups_lang_t  *lang;                  /* Current language */
+  ppd_attr_t   *attr,                  /* Current attribute */
+               *locattr;               /* Localized attribute */
   char         ckeyword[PPD_MAX_NAME], /* Custom keyword */
-               ll_CC[6],               /* Language + country locale */
-               ll[3];                  /* Language locale */
-  const char   *text;                  /* Localized text */
+               ll_CC[6];               /* Language + country locale */
 
 
  /*
   * Range check input...
   */
 
-  DEBUG_printf(("ppdLocalize(ppd=%p)\n", ppd));
+  DEBUG_printf(("ppdLocalize(ppd=%p)", ppd));
 
   if (!ppd)
     return (-1);
@@ -89,14 +92,7 @@ ppdLocalize(ppd_file_t *ppd)         /* I - PPD file */
   * Get the default language...
   */
 
-  if ((lang = cupsLangDefault()) == NULL)
-    return (-1);
-
-  strlcpy(ll_CC, lang->language, sizeof(ll_CC));
-  strlcpy(ll, lang->language, sizeof(ll));
-
-  DEBUG_printf(("    lang->language=\"%s\", ll=\"%s\", ll_CC=\"%s\"...\n",
-                lang->language, ll, ll_CC));
+  ppd_ll_CC(ll_CC, sizeof(ll_CC));
 
  /*
   * Now lookup all of the groups, options, choices, etc.
@@ -104,30 +100,33 @@ ppdLocalize(ppd_file_t *ppd)              /* I - PPD file */
 
   for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++)
   {
-    if ((text = ppd_text(ppd, "Translation", group->name, ll_CC, ll)) != NULL)
-      strlcpy(group->text, text, sizeof(group->text));
+    if ((locattr = _ppdLocalizedAttr(ppd, "Translation", group->name,
+                                     ll_CC)) != NULL)
+      strlcpy(group->text, locattr->text, sizeof(group->text));
 
     for (j = group->num_options, option = group->options; j > 0; j --, option ++)
     {
-      if ((text = ppd_text(ppd, "Translation", option->keyword, ll_CC,
-                           ll)) != NULL)
-       strlcpy(option->text, text, sizeof(option->text));
+      if ((locattr = _ppdLocalizedAttr(ppd, "Translation", option->keyword,
+                                       ll_CC)) != NULL)
+       strlcpy(option->text, locattr->text, sizeof(option->text));
 
       for (k = option->num_choices, choice = option->choices;
            k > 0;
           k --, choice ++)
       {
-        if (strcmp(choice->choice, "Custom"))
-         text = ppd_text(ppd, option->keyword, choice->choice, ll_CC, ll);
+        if (strcmp(choice->choice, "Custom") ||
+           !ppdFindCustomOption(ppd, option->keyword))
+         locattr = _ppdLocalizedAttr(ppd, option->keyword, choice->choice,
+                                     ll_CC);
        else
        {
          snprintf(ckeyword, sizeof(ckeyword), "Custom%s", option->keyword);
 
-         text = ppd_text(ppd, ckeyword, "True", ll_CC, ll);
+         locattr = _ppdLocalizedAttr(ppd, ckeyword, "True", ll_CC);
        }
 
-        if (text)
-         strlcpy(choice->text, text, sizeof(choice->text));
+        if (locattr)
+         strlcpy(choice->text, locattr->text, sizeof(choice->text));
       }
     }
   }
@@ -146,33 +145,532 @@ ppdLocalize(ppd_file_t *ppd)             /* I - PPD file */
     {
       snprintf(ckeyword, sizeof(ckeyword), "ParamCustom%s", coption->keyword);
 
-      if ((text = ppd_text(ppd, ckeyword, cparam->name, ll_CC, ll)) != NULL)
-        strlcpy(cparam->text, text, sizeof(cparam->text));
+      if ((locattr = _ppdLocalizedAttr(ppd, ckeyword, cparam->name,
+                                       ll_CC)) != NULL)
+        strlcpy(cparam->text, locattr->text, sizeof(cparam->text));
     }
   }
 
+ /*
+  * Translate ICC profile names...
+  */
+
+  if ((attr = ppdFindAttr(ppd, "APCustomColorMatchingName", NULL)) != NULL)
+  {
+    if ((locattr = _ppdLocalizedAttr(ppd, "APCustomColorMatchingName",
+                                     attr->spec, ll_CC)) != NULL)
+      strlcpy(attr->text, locattr->text, sizeof(attr->text));
+  }
+
+  for (attr = ppdFindAttr(ppd, "cupsICCProfile", NULL);
+       attr;
+       attr = ppdFindNextAttr(ppd, "cupsICCProfile", NULL))
+  {
+    cupsArraySave(ppd->sorted_attrs);
+
+    if ((locattr = _ppdLocalizedAttr(ppd, "cupsICCProfile", attr->spec,
+                                     ll_CC)) != NULL)
+      strlcpy(attr->text, locattr->text, sizeof(attr->text));
+
+    cupsArrayRestore(ppd->sorted_attrs);
+  }
+
+ /*
+  * Translate printer presets...
+  */
+
+  for (attr = ppdFindAttr(ppd, "APPrinterPreset", NULL);
+       attr;
+       attr = ppdFindNextAttr(ppd, "APPrinterPreset", NULL))
+  {
+    cupsArraySave(ppd->sorted_attrs);
+
+    if ((locattr = _ppdLocalizedAttr(ppd, "APPrinterPreset", attr->spec,
+                                     ll_CC)) != NULL)
+      strlcpy(attr->text, locattr->text, sizeof(attr->text));
+
+    cupsArrayRestore(ppd->sorted_attrs);
+  }
+
   return (0);
 }
 
 
 /*
- * 'ppd_text()' - Find the localized text as needed...
+ * 'ppdLocalizeAttr()' - Localize an attribute.
+ *
+ * This function uses the current locale to find the localized attribute for
+ * the given main and option keywords.  If no localized version of the
+ * attribute exists for the current locale, the unlocalized version is returned.
  */
 
-static const char *                    /* O - Localized text or NULL */
-ppd_text(ppd_file_t *ppd,              /* I - PPD file */
-         const char *keyword,          /* I - Main keyword */
-         const char *spec,             /* I - Option keyword */
-        const char *ll_CC,             /* I - Language + country locale */
-        const char *ll)                /* I - Language locale */
+ppd_attr_t *                           /* O - Localized attribute or @code NULL@ if none exists */
+ppdLocalizeAttr(ppd_file_t *ppd,       /* I - PPD file */
+               const char *keyword,    /* I - Main keyword */
+               const char *spec)       /* I - Option keyword or @code NULL@ for none */
+{
+  ppd_attr_t   *locattr;               /* Localized attribute */
+  char         ll_CC[6];               /* Language + country locale */
+
+
+ /*
+  * Get the default language...
+  */
+
+  ppd_ll_CC(ll_CC, sizeof(ll_CC));
+
+ /*
+  * Find the localized attribute...
+  */
+
+  if (spec)
+    locattr = _ppdLocalizedAttr(ppd, keyword, spec, ll_CC);
+  else
+    locattr = _ppdLocalizedAttr(ppd, "Translation", keyword, ll_CC);
+
+  if (!locattr)
+    locattr = ppdFindAttr(ppd, keyword, spec);
+
+  return (locattr);
+}
+
+
+/*
+ * 'ppdLocalizeIPPReason()' - Get the localized version of a cupsIPPReason
+ *                            attribute.
+ *
+ * This function uses the current locale to find the corresponding reason
+ * text or URI from the attribute value. If "scheme" is NULL or "text",
+ * the returned value contains human-readable (UTF-8) text from the translation
+ * string or attribute value. Otherwise the corresponding URI is returned.
+ *
+ * If no value of the requested scheme can be found, NULL is returned.
+ *
+ * @since CUPS 1.3/Mac OS X 10.5@
+ */
+
+const char *                           /* O - Value or NULL if not found */
+ppdLocalizeIPPReason(
+    ppd_file_t *ppd,                   /* I - PPD file */
+    const char *reason,                        /* I - IPP reason keyword to look up */
+    const char *scheme,                        /* I - URI scheme or NULL for text */
+    char       *buffer,                        /* I - Value buffer */
+    size_t     bufsize)                        /* I - Size of value buffer */
+{
+  cups_lang_t  *lang;                  /* Current language */
+  ppd_attr_t   *locattr;               /* Localized attribute */
+  char         ll_CC[6],               /* Language + country locale */
+               *bufptr,                /* Pointer into buffer */
+               *bufend,                /* Pointer to end of buffer */
+               *valptr;                /* Pointer into value */
+  int          ch,                     /* Hex-encoded character */
+               schemelen;              /* Length of scheme name */
+
+
+ /*
+  * Range check input...
+  */
+
+  if (buffer)
+    *buffer = '\0';
+
+  if (!ppd || !reason || (scheme && !*scheme) ||
+      !buffer || bufsize < PPD_MAX_TEXT)
+    return (NULL);
+
+ /*
+  * Get the default language...
+  */
+
+  lang = ppd_ll_CC(ll_CC, sizeof(ll_CC));
+
+ /*
+  * Find the localized attribute...
+  */
+
+  if ((locattr = _ppdLocalizedAttr(ppd, "cupsIPPReason", reason,
+                                   ll_CC)) == NULL)
+    locattr = ppdFindAttr(ppd, "cupsIPPReason", reason);
+
+  if (!locattr)
+  {
+    if (lang && (!scheme || !strcmp(scheme, "text")))
+    {
+     /*
+      * Try to localize a standard printer-state-reason keyword...
+      */
+
+      const char *message = NULL;      /* Localized message */
+
+      if (!strncmp(reason, "media-needed", 12))
+       message = _("The paper tray needs to be filled.");
+      else if (!strncmp(reason, "media-jam", 9))
+       message = _("There is a paper jam.");
+      else if (!strncmp(reason, "offline", 7) ||
+                      !strncmp(reason, "shutdown", 8))
+       message = _("The printer is not connected.");
+      else if (!strncmp(reason, "toner-low", 9))
+       message = _("The printer is low on toner.");
+      else if (!strncmp(reason, "toner-empty", 11))
+       message = _("The printer is out of toner.");
+      else if (!strncmp(reason, "cover-open", 10))
+       message = _("The printer's cover is open.");
+      else if (!strncmp(reason, "interlock-open", 14))
+       message = _("The printer's interlock is open.");
+      else if (!strncmp(reason, "door-open", 9))
+       message = _("The printer's door is open.");
+      else if (!strncmp(reason, "input-tray-missing", 18))
+       message = _("The paper tray is missing.");
+      else if (!strncmp(reason, "media-low", 9))
+       message = _("The paper tray is almost empty.");
+      else if (!strncmp(reason, "media-empty", 11))
+       message = _("The paper tray is empty.");
+      else if (!strncmp(reason, "output-tray-missing", 19))
+       message = _("The output bin is missing.");
+      else if (!strncmp(reason, "output-area-almost-full", 23))
+       message = _("The output bin is almost full.");
+      else if (!strncmp(reason, "output-area-full", 16))
+       message = _("The output bin is full.");
+      else if (!strncmp(reason, "marker-supply-low", 17))
+       message = _("The printer is almost out of ink.");               
+      else if (!strncmp(reason, "marker-supply-empty", 19))
+       message = _("The printer is out of ink.");
+      else if (!strncmp(reason, "marker-waste-almost-full", 24))
+       message = _("The printer's waste bin is almost full.");
+      else if (!strncmp(reason, "marker-waste-full", 17))
+       message = _("The printer's waste bin is full.");
+      else if (!strncmp(reason, "fuser-over-temp", 15))
+       message = _("The fuser's temperature is high.");
+      else if (!strncmp(reason, "fuser-under-temp", 16))
+       message = _("The fuser's temperature is low.");
+      else if (!strncmp(reason, "opc-near-eol", 12))
+       message = _("The optical photoconductor will need to be replaced soon.");
+      else if (!strncmp(reason, "opc-life-over", 13))
+       message = _("The optical photoconductor needs to be replaced.");
+      else if (!strncmp(reason, "developer-low", 13))
+       message = _("The developer unit will need to be replaced soon.");
+      else if (!strncmp(reason, "developer-empty", 15))
+       message = _("The developer unit needs to be replaced.");
+               
+      if (message)
+      {
+        strlcpy(buffer, _cupsLangString(lang, message), bufsize);
+       return (buffer);
+      }
+    }
+
+    return (NULL);
+  }
+
+ /*
+  * Now find the value we need...
+  */
+
+  bufend = buffer + bufsize - 1;
+
+  if (!scheme || !strcmp(scheme, "text"))
+  {
+   /*
+    * Copy a text value (either the translation text or text:... URIs from
+    * the value...
+    */
+
+    strlcpy(buffer, locattr->text, bufsize);
+
+    for (valptr = locattr->value, bufptr = buffer; *valptr && bufptr < bufend;)
+    {
+      if (!strncmp(valptr, "text:", 5))
+      {
+       /*
+        * Decode text: URI and add to the buffer...
+       */
+
+       valptr += 5;
+
+        while (*valptr && !_cups_isspace(*valptr) && bufptr < bufend)
+       {
+         if (*valptr == '%' && isxdigit(valptr[1] & 255) &&
+             isxdigit(valptr[2] & 255))
+         {
+          /*
+           * Pull a hex-encoded character from the URI...
+           */
+
+            valptr ++;
+
+           if (isdigit(*valptr & 255))
+             ch = (*valptr - '0') << 4;
+           else
+             ch = (tolower(*valptr) - 'a' + 10) << 4;
+           valptr ++;
+
+           if (isdigit(*valptr & 255))
+             *bufptr++ = ch | (*valptr - '0');
+           else
+             *bufptr++ = ch | (tolower(*valptr) - 'a' + 10);
+           valptr ++;
+         }
+         else if (*valptr == '+')
+         {
+           *bufptr++ = ' ';
+           valptr ++;
+         }
+         else
+           *bufptr++ = *valptr++;
+        }
+      }
+      else
+      {
+       /*
+        * Skip this URI...
+       */
+
+        while (*valptr && !_cups_isspace(*valptr))
+          valptr++;
+      }
+
+     /*
+      * Skip whitespace...
+      */
+
+      while (_cups_isspace(*valptr))
+       valptr ++;
+    }
+
+    if (bufptr > buffer)
+      *bufptr = '\0';
+
+    return (buffer);
+  }
+  else
+  {
+   /*
+    * Copy a URI...
+    */
+
+    schemelen = strlen(scheme);
+    if (scheme[schemelen - 1] == ':')  /* Force scheme to be just the name */
+      schemelen --;
+
+    for (valptr = locattr->value, bufptr = buffer; *valptr && bufptr < bufend;)
+    {
+      if ((!strncmp(valptr, scheme, schemelen) && valptr[schemelen] == ':') ||
+          (*valptr == '/' && !strcmp(scheme, "file")))
+      {
+       /*
+        * Copy URI...
+       */
+
+        while (*valptr && !_cups_isspace(*valptr) && bufptr < bufend)
+         *bufptr++ = *valptr++;
+
+       *bufptr = '\0';
+
+       return (buffer);
+      }
+      else
+      {
+       /*
+        * Skip this URI...
+       */
+
+       while (*valptr && !_cups_isspace(*valptr))
+         valptr++;
+      }
+
+     /*
+      * Skip whitespace...
+      */
+
+      while (_cups_isspace(*valptr))
+       valptr ++;
+    }
+
+    return (NULL);
+  }
+}
+
+
+/*
+ * 'ppdLocalizeMarkerName()' - Get the localized version of a marker-names
+ *                             attribute value.
+ *
+ * This function uses the current locale to find the corresponding name
+ * text from the attribute value. If no localized text for the requested
+ * name can be found, @code NULL@ is returned.
+ *
+ * @since CUPS 1.4/Mac OS X 10.6@
+ */
+
+const char *                           /* O - Value or @code NULL@ if not found */
+ppdLocalizeMarkerName(
+    ppd_file_t *ppd,                   /* I - PPD file */
+    const char *name)                  /* I - Marker name to look up */
+{
+  ppd_attr_t   *locattr;               /* Localized attribute */
+  char         ll_CC[6];               /* Language + country locale */
+
+
+ /*
+  * Range check input...
+  */
+
+  if (!ppd || !name)
+    return (NULL);
+
+ /*
+  * Get the default language...
+  */
+
+  ppd_ll_CC(ll_CC, sizeof(ll_CC));
+
+ /*
+  * Find the localized attribute...
+  */
+
+  if ((locattr = _ppdLocalizedAttr(ppd, "cupsMarkerName", name,
+                                   ll_CC)) == NULL)
+    locattr = ppdFindAttr(ppd, "cupsMarkerName", name);
+
+  return (locattr ? locattr->text : NULL);
+}
+
+
+/*
+ * '_ppdFreeLanguages()' - Free an array of languages from _ppdGetLanguages.
+ */
+
+void
+_ppdFreeLanguages(
+    cups_array_t *languages)           /* I - Languages array */
+{
+  char *language;                      /* Current language */
+
+
+  for (language = (char *)cupsArrayFirst(languages);
+       language;
+       language = (char *)cupsArrayNext(languages))
+    free(language);
+
+  cupsArrayDelete(languages);
+}
+
+
+/*
+ * '_ppdGetLanguages()' - Get an array of languages from a PPD file.
+ */
+
+cups_array_t *                         /* O - Languages array */
+_ppdGetLanguages(ppd_file_t *ppd)      /* I - PPD file */
+{
+  cups_array_t *languages;             /* Languages array */
+  ppd_attr_t   *attr;                  /* cupsLanguages attribute */
+  char         *value,                 /* Copy of attribute value */
+               *start,                 /* Start of current language */
+               *ptr;                   /* Pointer into languages */
+
+
+ /*
+  * See if we have a cupsLanguages attribute...
+  */
+
+  if ((attr = ppdFindAttr(ppd, "cupsLanguages", NULL)) == NULL || !attr->value)
+    return (NULL);
+
+ /*
+  * Yes, load the list...
+  */
+
+  if ((languages = cupsArrayNew((cups_array_func_t)strcmp, NULL)) == NULL)
+    return (NULL);
+
+  if ((value = strdup(attr->value)) == NULL)
+  {
+    cupsArrayDelete(languages);
+    return (NULL);
+  }
+
+  for (ptr = value; *ptr;)
+  {
+   /*
+    * Skip leading whitespace...
+    */
+
+    while (_cups_isspace(*ptr))
+      ptr ++;
+
+    if (!*ptr)
+      break;
+
+   /*
+    * Find the end of this language name...
+    */
+
+    for (start = ptr; *ptr && !_cups_isspace(*ptr); ptr ++);
+
+    if (*ptr)
+      *ptr++ = '\0';
+
+    if (!strcmp(start, "en"))
+      continue;
+
+    cupsArrayAdd(languages, strdup(start));
+  }
+
+ /*
+  * Free the temporary string and return either an array with one or more
+  * values or a NULL pointer...
+  */
+
+  free(value);
+
+  if (cupsArrayCount(languages) == 0)
+  {
+    cupsArrayDelete(languages);
+    return (NULL);
+  }
+  else
+    return (languages);
+}
+
+
+/*
+ * '_ppdHashName()' - Generate a hash value for a device or profile name.
+ *
+ * This function is primarily used on Mac OS X, but is generally accessible
+ * since cupstestppd needs to check for profile name collisions in PPD files...
+ */
+
+unsigned                               /* O - Hash value */
+_ppdHashName(const char *name)         /* I - Name to hash */
+{
+  int          mult;                   /* Multiplier */
+  unsigned     hash = 0;               /* Hash value */
+
+
+  for (mult = 1; *name && mult <= 128; mult ++, name ++)
+    hash += (*name & 255) * mult;
+
+  return (hash);
+}
+
+
+/*
+ * '_ppdLocalizedAttr()' - Find a localized attribute.
+ */
+
+ppd_attr_t *                           /* O - Localized attribute or NULL */
+_ppdLocalizedAttr(ppd_file_t *ppd,     /* I - PPD file */
+                 const char *keyword,  /* I - Main keyword */
+                 const char *spec,     /* I - Option keyword */
+                 const char *ll_CC)    /* I - Language + country locale */
 {
   char         lkeyword[PPD_MAX_NAME]; /* Localization keyword */
   ppd_attr_t   *attr;                  /* Current attribute */
 
 
-  DEBUG_printf(("ppd_text(ppd=%p, keyword=\"%s\", spec=\"%s\", "
-                "ll_CC=\"%s\", ll=\"%s\")\n",
-                ppd, keyword, spec, ll_CC, ll));
+  DEBUG_printf(("4_ppdLocalizedAttr(ppd=%p, keyword=\"%s\", spec=\"%s\", "
+                "ll_CC=\"%s\")", ppd, keyword, spec, ll_CC));
 
  /*
   * Look for Keyword.ll_CC, then Keyword.ll...
@@ -181,26 +679,101 @@ ppd_text(ppd_file_t *ppd,                /* I - PPD file */
   snprintf(lkeyword, sizeof(lkeyword), "%s.%s", ll_CC, keyword);
   if ((attr = ppdFindAttr(ppd, lkeyword, spec)) == NULL)
   {
-    snprintf(lkeyword, sizeof(lkeyword), "%s.%s", ll, keyword);
+    snprintf(lkeyword, sizeof(lkeyword), "%2.2s.%s", ll_CC, keyword);
     attr = ppdFindAttr(ppd, lkeyword, spec);
+
+    if (!attr)
+    {
+      if (!strncmp(ll_CC, "ja", 2))
+      {
+       /*
+       * Due to a bug in the CUPS DDK 1.1.0 ppdmerge program, Japanese
+       * PPD files were incorrectly assigned "jp" as the locale name
+       * instead of "ja".  Support both the old (incorrect) and new
+       * locale names for Japanese...
+       */
+
+       snprintf(lkeyword, sizeof(lkeyword), "jp.%s", keyword);
+       attr = ppdFindAttr(ppd, lkeyword, spec);
+      }
+      else if (!strncmp(ll_CC, "no", 2))
+      {
+       /*
+       * Norway has two languages, "Bokmal" (the primary one)
+       * and "Nynorsk" (new Norwegian); we map "no" to "nb" here as
+       * recommended by the locale folks...
+       */
+
+       snprintf(lkeyword, sizeof(lkeyword), "nb.%s", keyword);
+       attr = ppdFindAttr(ppd, lkeyword, spec);
+      }
+    }
   }
 
 #ifdef DEBUG
   if (attr)
-    printf("    *%s %s/%s: \"%s\"\n", attr->name, attr->spec, attr->text,
-           attr->value ? attr->value : "");
+    DEBUG_printf(("5_ppdLocalizedAttr: *%s %s/%s: \"%s\"\n", attr->name,
+                  attr->spec, attr->text, attr->value ? attr->value : ""));
   else
-    puts("    NOT FOUND");
+    DEBUG_puts("5_ppdLocalizedAttr: NOT FOUND");
 #endif /* DEBUG */
 
+  return (attr);
+}
+
+
+/*
+ * 'ppd_ll_CC()' - Get the current locale names.
+ */
+
+static cups_lang_t *                   /* O - Current language */
+ppd_ll_CC(char *ll_CC,                 /* O - Country-specific locale name */
+          int  ll_CC_size)             /* I - Size of country-specific name */
+{
+  cups_lang_t  *lang;                  /* Current language */
+
+
+ /*
+  * Get the current locale...
+  */
+
+  if ((lang = cupsLangDefault()) == NULL)
+  {
+    strlcpy(ll_CC, "en_US", ll_CC_size);
+    return (NULL);
+  }
+
  /*
-  * Return text if we find it...
+  * Copy the locale name...
   */
 
-  return (attr ? attr->text : NULL);
+  strlcpy(ll_CC, lang->language, ll_CC_size);
+
+  if (strlen(ll_CC) == 2)
+  {
+   /*
+    * Map "ll" to primary/origin country locales to have the best
+    * chance of finding a match...
+    */
+
+    if (!strcmp(ll_CC, "cs"))
+      strlcpy(ll_CC, "cs_CZ", ll_CC_size);
+    else if (!strcmp(ll_CC, "en"))
+      strlcpy(ll_CC, "en_US", ll_CC_size);
+    else if (!strcmp(ll_CC, "ja"))
+      strlcpy(ll_CC, "ja_JP", ll_CC_size);
+    else if (!strcmp(ll_CC, "sv"))
+      strlcpy(ll_CC, "sv_SE", ll_CC_size);
+    else if (!strcmp(ll_CC, "zh"))     /* Simplified Chinese */
+      strlcpy(ll_CC, "zh_CN", ll_CC_size);
+  }
+
+  DEBUG_printf(("8ppd_ll_CC: lang->language=\"%s\", ll_CC=\"%s\"...",
+                lang->language, ll_CC));
+  return (lang);
 }
 
 
 /*
- * End of "$Id: localize.c 5824 2006-08-15 18:19:45Z mike $".
+ * End of "$Id: localize.c 7679 2008-06-19 23:37:45Z mike $".
  */