1/*
2 * "$Id: localize.c 11093 2013-07-03 20:48:42Z msweet $"
3 *
4 *   PPD localization routines for CUPS.
5 *
6 *   Copyright 2007-2012 by Apple Inc.
7 *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
8 *
9 *   These coded instructions, statements, and computer programs are the
10 *   property of Apple Inc. and are protected by Federal copyright
11 *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
12 *   which should have been included with this file.  If this file is
13 *   file is missing or damaged, see the license at "http://www.cups.org/".
14 *
15 *   PostScript is a trademark of Adobe Systems, Inc.
16 *
17 *   This code and any derivative of it may be used and distributed
18 *   freely under the terms of the GNU General Public License when
19 *   used with GNU Ghostscript or its derivatives.  Use of the code
20 *   (or any derivative of it) with software other than GNU
21 *   GhostScript (or its derivatives) is governed by the CUPS license
22 *   agreement.
23 *
24 *   This file is subject to the Apple OS-Developed Software exception.
25 *
26 * Contents:
27 *
28 *   ppdLocalize()           - Localize the PPD file to the current locale.
29 *   ppdLocalizeAttr()       - Localize an attribute.
30 *   ppdLocalizeIPPReason()  - Get the localized version of a cupsIPPReason
31 *                             attribute.
32 *   ppdLocalizeMarkerName() - Get the localized version of a marker-names
33 *                             attribute value.
34 *   _ppdFreeLanguages()     - Free an array of languages from _ppdGetLanguages.
35 *   _ppdGetLanguages()      - Get an array of languages from a PPD file.
36 *   _ppdHashName()          - Generate a hash value for a device or profile
37 *                             name.
38 *   _ppdLocalizedAttr()     - Find a localized attribute.
39 *   ppd_ll_CC()             - Get the current locale names.
40 */
41
42/*
43 * Include necessary headers.
44 */
45
46#include "cups-private.h"
47#include "ppd-private.h"
48
49
50/*
51 * Local functions...
52 */
53
54static cups_lang_t	*ppd_ll_CC(char *ll_CC, int ll_CC_size);
55
56
57/*
58 * 'ppdLocalize()' - Localize the PPD file to the current locale.
59 *
60 * All groups, options, and choices are localized, as are ICC profile
61 * descriptions, printer presets, and custom option parameters.  Each
62 * localized string uses the UTF-8 character encoding.
63 *
64 * @since CUPS 1.2/OS X 10.5@
65 */
66
67int					/* O - 0 on success, -1 on error */
68ppdLocalize(ppd_file_t *ppd)		/* I - PPD file */
69{
70  int		i, j, k;		/* Looping vars */
71  ppd_group_t	*group;			/* Current group */
72  ppd_option_t	*option;		/* Current option */
73  ppd_choice_t	*choice;		/* Current choice */
74  ppd_coption_t	*coption;		/* Current custom option */
75  ppd_cparam_t	*cparam;		/* Current custom parameter */
76  ppd_attr_t	*attr,			/* Current attribute */
77		*locattr;		/* Localized attribute */
78  char		ckeyword[PPD_MAX_NAME],	/* Custom keyword */
79		ll_CC[6];		/* Language + country locale */
80
81
82 /*
83  * Range check input...
84  */
85
86  DEBUG_printf(("ppdLocalize(ppd=%p)", ppd));
87
88  if (!ppd)
89    return (-1);
90
91 /*
92  * Get the default language...
93  */
94
95  ppd_ll_CC(ll_CC, sizeof(ll_CC));
96
97 /*
98  * Now lookup all of the groups, options, choices, etc.
99  */
100
101  for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++)
102  {
103    if ((locattr = _ppdLocalizedAttr(ppd, "Translation", group->name,
104                                     ll_CC)) != NULL)
105      strlcpy(group->text, locattr->text, sizeof(group->text));
106
107    for (j = group->num_options, option = group->options; j > 0; j --, option ++)
108    {
109      if ((locattr = _ppdLocalizedAttr(ppd, "Translation", option->keyword,
110                                       ll_CC)) != NULL)
111	strlcpy(option->text, locattr->text, sizeof(option->text));
112
113      for (k = option->num_choices, choice = option->choices;
114           k > 0;
115	   k --, choice ++)
116      {
117        if (strcmp(choice->choice, "Custom") ||
118	    !ppdFindCustomOption(ppd, option->keyword))
119	  locattr = _ppdLocalizedAttr(ppd, option->keyword, choice->choice,
120	                              ll_CC);
121	else
122	{
123	  snprintf(ckeyword, sizeof(ckeyword), "Custom%s", option->keyword);
124
125	  locattr = _ppdLocalizedAttr(ppd, ckeyword, "True", ll_CC);
126	}
127
128        if (locattr)
129	  strlcpy(choice->text, locattr->text, sizeof(choice->text));
130      }
131    }
132  }
133
134 /*
135  * Translate any custom parameters...
136  */
137
138  for (coption = (ppd_coption_t *)cupsArrayFirst(ppd->coptions);
139       coption;
140       coption = (ppd_coption_t *)cupsArrayNext(ppd->coptions))
141  {
142    for (cparam = (ppd_cparam_t *)cupsArrayFirst(coption->params);
143	 cparam;
144	 cparam = (ppd_cparam_t *)cupsArrayNext(coption->params))
145    {
146      snprintf(ckeyword, sizeof(ckeyword), "ParamCustom%s", coption->keyword);
147
148      if ((locattr = _ppdLocalizedAttr(ppd, ckeyword, cparam->name,
149                                       ll_CC)) != NULL)
150        strlcpy(cparam->text, locattr->text, sizeof(cparam->text));
151    }
152  }
153
154 /*
155  * Translate ICC profile names...
156  */
157
158  if ((attr = ppdFindAttr(ppd, "APCustomColorMatchingName", NULL)) != NULL)
159  {
160    if ((locattr = _ppdLocalizedAttr(ppd, "APCustomColorMatchingName",
161                                     attr->spec, ll_CC)) != NULL)
162      strlcpy(attr->text, locattr->text, sizeof(attr->text));
163  }
164
165  for (attr = ppdFindAttr(ppd, "cupsICCProfile", NULL);
166       attr;
167       attr = ppdFindNextAttr(ppd, "cupsICCProfile", NULL))
168  {
169    cupsArraySave(ppd->sorted_attrs);
170
171    if ((locattr = _ppdLocalizedAttr(ppd, "cupsICCProfile", attr->spec,
172                                     ll_CC)) != NULL)
173      strlcpy(attr->text, locattr->text, sizeof(attr->text));
174
175    cupsArrayRestore(ppd->sorted_attrs);
176  }
177
178 /*
179  * Translate printer presets...
180  */
181
182  for (attr = ppdFindAttr(ppd, "APPrinterPreset", NULL);
183       attr;
184       attr = ppdFindNextAttr(ppd, "APPrinterPreset", NULL))
185  {
186    cupsArraySave(ppd->sorted_attrs);
187
188    if ((locattr = _ppdLocalizedAttr(ppd, "APPrinterPreset", attr->spec,
189                                     ll_CC)) != NULL)
190      strlcpy(attr->text, locattr->text, sizeof(attr->text));
191
192    cupsArrayRestore(ppd->sorted_attrs);
193  }
194
195  return (0);
196}
197
198
199/*
200 * 'ppdLocalizeAttr()' - Localize an attribute.
201 *
202 * This function uses the current locale to find the localized attribute for
203 * the given main and option keywords.  If no localized version of the
204 * attribute exists for the current locale, the unlocalized version is returned.
205 */
206
207ppd_attr_t *				/* O - Localized attribute or @code NULL@ if none exists */
208ppdLocalizeAttr(ppd_file_t *ppd,	/* I - PPD file */
209		const char *keyword,	/* I - Main keyword */
210		const char *spec)	/* I - Option keyword or @code NULL@ for none */
211{
212  ppd_attr_t	*locattr;		/* Localized attribute */
213  char		ll_CC[6];		/* Language + country locale */
214
215
216 /*
217  * Get the default language...
218  */
219
220  ppd_ll_CC(ll_CC, sizeof(ll_CC));
221
222 /*
223  * Find the localized attribute...
224  */
225
226  if (spec)
227    locattr = _ppdLocalizedAttr(ppd, keyword, spec, ll_CC);
228  else
229    locattr = _ppdLocalizedAttr(ppd, "Translation", keyword, ll_CC);
230
231  if (!locattr)
232    locattr = ppdFindAttr(ppd, keyword, spec);
233
234  return (locattr);
235}
236
237
238/*
239 * 'ppdLocalizeIPPReason()' - Get the localized version of a cupsIPPReason
240 *                            attribute.
241 *
242 * This function uses the current locale to find the corresponding reason
243 * text or URI from the attribute value. If "scheme" is NULL or "text",
244 * the returned value contains human-readable (UTF-8) text from the translation
245 * string or attribute value. Otherwise the corresponding URI is returned.
246 *
247 * If no value of the requested scheme can be found, NULL is returned.
248 *
249 * @since CUPS 1.3/OS X 10.5@
250 */
251
252const char *				/* O - Value or NULL if not found */
253ppdLocalizeIPPReason(
254    ppd_file_t *ppd,			/* I - PPD file */
255    const char *reason,			/* I - IPP reason keyword to look up */
256    const char *scheme,			/* I - URI scheme or NULL for text */
257    char       *buffer,			/* I - Value buffer */
258    size_t     bufsize)			/* I - Size of value buffer */
259{
260  cups_lang_t	*lang;			/* Current language */
261  ppd_attr_t	*locattr;		/* Localized attribute */
262  char		ll_CC[6],		/* Language + country locale */
263		*bufptr,		/* Pointer into buffer */
264		*bufend,		/* Pointer to end of buffer */
265		*valptr;		/* Pointer into value */
266  int		ch,			/* Hex-encoded character */
267		schemelen;		/* Length of scheme name */
268
269
270 /*
271  * Range check input...
272  */
273
274  if (buffer)
275    *buffer = '\0';
276
277  if (!ppd || !reason || (scheme && !*scheme) ||
278      !buffer || bufsize < PPD_MAX_TEXT)
279    return (NULL);
280
281 /*
282  * Get the default language...
283  */
284
285  lang = ppd_ll_CC(ll_CC, sizeof(ll_CC));
286
287 /*
288  * Find the localized attribute...
289  */
290
291  if ((locattr = _ppdLocalizedAttr(ppd, "cupsIPPReason", reason,
292                                   ll_CC)) == NULL)
293    locattr = ppdFindAttr(ppd, "cupsIPPReason", reason);
294
295  if (!locattr)
296  {
297    if (lang && (!scheme || !strcmp(scheme, "text")))
298    {
299     /*
300      * Try to localize a standard printer-state-reason keyword...
301      */
302
303      const char *message = NULL;	/* Localized message */
304
305      if (!strncmp(reason, "media-needed", 12))
306	message = _("The paper tray needs to be filled.");
307      else if (!strncmp(reason, "media-jam", 9))
308	message = _("There is a paper jam.");
309      else if (!strncmp(reason, "offline", 7) ||
310		       !strncmp(reason, "shutdown", 8))
311	message = _("The printer is not connected.");
312      else if (!strncmp(reason, "toner-low", 9))
313	message = _("The printer is running low on toner.");
314      else if (!strncmp(reason, "toner-empty", 11))
315	message = _("The printer may be out of toner.");
316      else if (!strncmp(reason, "cover-open", 10))
317	message = _("The printer's cover is open.");
318      else if (!strncmp(reason, "interlock-open", 14))
319	message = _("The printer's interlock is open.");
320      else if (!strncmp(reason, "door-open", 9))
321	message = _("The printer's door is open.");
322      else if (!strncmp(reason, "input-tray-missing", 18))
323	message = _("The paper tray is missing.");
324      else if (!strncmp(reason, "media-low", 9))
325	message = _("The paper tray is almost empty.");
326      else if (!strncmp(reason, "media-empty", 11))
327	message = _("The paper tray is empty.");
328      else if (!strncmp(reason, "output-tray-missing", 19))
329	message = _("The output bin is missing.");
330      else if (!strncmp(reason, "output-area-almost-full", 23))
331	message = _("The output bin is almost full.");
332      else if (!strncmp(reason, "output-area-full", 16))
333	message = _("The output bin is full.");
334      else if (!strncmp(reason, "marker-supply-low", 17))
335	message = _("The printer is running low on ink.");
336      else if (!strncmp(reason, "marker-supply-empty", 19))
337	message = _("The printer may be out of ink.");
338      else if (!strncmp(reason, "marker-waste-almost-full", 24))
339	message = _("The printer's waste bin is almost full.");
340      else if (!strncmp(reason, "marker-waste-full", 17))
341	message = _("The printer's waste bin is full.");
342      else if (!strncmp(reason, "fuser-over-temp", 15))
343	message = _("The fuser's temperature is high.");
344      else if (!strncmp(reason, "fuser-under-temp", 16))
345	message = _("The fuser's temperature is low.");
346      else if (!strncmp(reason, "opc-near-eol", 12))
347	message = _("The optical photoconductor will need to be replaced soon.");
348      else if (!strncmp(reason, "opc-life-over", 13))
349	message = _("The optical photoconductor needs to be replaced.");
350      else if (!strncmp(reason, "developer-low", 13))
351	message = _("The developer unit will need to be replaced soon.");
352      else if (!strncmp(reason, "developer-empty", 15))
353	message = _("The developer unit needs to be replaced.");
354
355      if (message)
356      {
357        strlcpy(buffer, _cupsLangString(lang, message), bufsize);
358	return (buffer);
359      }
360    }
361
362    return (NULL);
363  }
364
365 /*
366  * Now find the value we need...
367  */
368
369  bufend = buffer + bufsize - 1;
370
371  if (!scheme || !strcmp(scheme, "text"))
372  {
373   /*
374    * Copy a text value (either the translation text or text:... URIs from
375    * the value...
376    */
377
378    strlcpy(buffer, locattr->text, bufsize);
379
380    for (valptr = locattr->value, bufptr = buffer; *valptr && bufptr < bufend;)
381    {
382      if (!strncmp(valptr, "text:", 5))
383      {
384       /*
385        * Decode text: URI and add to the buffer...
386	*/
387
388	valptr += 5;
389
390        while (*valptr && !_cups_isspace(*valptr) && bufptr < bufend)
391	{
392	  if (*valptr == '%' && isxdigit(valptr[1] & 255) &&
393	      isxdigit(valptr[2] & 255))
394	  {
395	   /*
396	    * Pull a hex-encoded character from the URI...
397	    */
398
399            valptr ++;
400
401	    if (isdigit(*valptr & 255))
402	      ch = (*valptr - '0') << 4;
403	    else
404	      ch = (tolower(*valptr) - 'a' + 10) << 4;
405	    valptr ++;
406
407	    if (isdigit(*valptr & 255))
408	      *bufptr++ = ch | (*valptr - '0');
409	    else
410	      *bufptr++ = ch | (tolower(*valptr) - 'a' + 10);
411	    valptr ++;
412	  }
413	  else if (*valptr == '+')
414	  {
415	    *bufptr++ = ' ';
416	    valptr ++;
417	  }
418	  else
419	    *bufptr++ = *valptr++;
420        }
421      }
422      else
423      {
424       /*
425        * Skip this URI...
426	*/
427
428        while (*valptr && !_cups_isspace(*valptr))
429          valptr++;
430      }
431
432     /*
433      * Skip whitespace...
434      */
435
436      while (_cups_isspace(*valptr))
437	valptr ++;
438    }
439
440    if (bufptr > buffer)
441      *bufptr = '\0';
442
443    return (buffer);
444  }
445  else
446  {
447   /*
448    * Copy a URI...
449    */
450
451    schemelen = strlen(scheme);
452    if (scheme[schemelen - 1] == ':')	/* Force scheme to be just the name */
453      schemelen --;
454
455    for (valptr = locattr->value, bufptr = buffer; *valptr && bufptr < bufend;)
456    {
457      if ((!strncmp(valptr, scheme, schemelen) && valptr[schemelen] == ':') ||
458          (*valptr == '/' && !strcmp(scheme, "file")))
459      {
460       /*
461        * Copy URI...
462	*/
463
464        while (*valptr && !_cups_isspace(*valptr) && bufptr < bufend)
465	  *bufptr++ = *valptr++;
466
467	*bufptr = '\0';
468
469	return (buffer);
470      }
471      else
472      {
473       /*
474        * Skip this URI...
475	*/
476
477	while (*valptr && !_cups_isspace(*valptr))
478	  valptr++;
479      }
480
481     /*
482      * Skip whitespace...
483      */
484
485      while (_cups_isspace(*valptr))
486	valptr ++;
487    }
488
489    return (NULL);
490  }
491}
492
493
494/*
495 * 'ppdLocalizeMarkerName()' - Get the localized version of a marker-names
496 *                             attribute value.
497 *
498 * This function uses the current locale to find the corresponding name
499 * text from the attribute value. If no localized text for the requested
500 * name can be found, @code NULL@ is returned.
501 *
502 * @since CUPS 1.4/OS X 10.6@
503 */
504
505const char *				/* O - Value or @code NULL@ if not found */
506ppdLocalizeMarkerName(
507    ppd_file_t *ppd,			/* I - PPD file */
508    const char *name)			/* I - Marker name to look up */
509{
510  ppd_attr_t	*locattr;		/* Localized attribute */
511  char		ll_CC[6];		/* Language + country locale */
512
513
514 /*
515  * Range check input...
516  */
517
518  if (!ppd || !name)
519    return (NULL);
520
521 /*
522  * Get the default language...
523  */
524
525  ppd_ll_CC(ll_CC, sizeof(ll_CC));
526
527 /*
528  * Find the localized attribute...
529  */
530
531  if ((locattr = _ppdLocalizedAttr(ppd, "cupsMarkerName", name,
532                                   ll_CC)) == NULL)
533    locattr = ppdFindAttr(ppd, "cupsMarkerName", name);
534
535  return (locattr ? locattr->text : NULL);
536}
537
538
539/*
540 * '_ppdFreeLanguages()' - Free an array of languages from _ppdGetLanguages.
541 */
542
543void
544_ppdFreeLanguages(
545    cups_array_t *languages)		/* I - Languages array */
546{
547  char	*language;			/* Current language */
548
549
550  for (language = (char *)cupsArrayFirst(languages);
551       language;
552       language = (char *)cupsArrayNext(languages))
553    free(language);
554
555  cupsArrayDelete(languages);
556}
557
558
559/*
560 * '_ppdGetLanguages()' - Get an array of languages from a PPD file.
561 */
562
563cups_array_t *				/* O - Languages array */
564_ppdGetLanguages(ppd_file_t *ppd)	/* I - PPD file */
565{
566  cups_array_t	*languages;		/* Languages array */
567  ppd_attr_t	*attr;			/* cupsLanguages attribute */
568  char		*value,			/* Copy of attribute value */
569		*start,			/* Start of current language */
570		*ptr;			/* Pointer into languages */
571
572
573 /*
574  * See if we have a cupsLanguages attribute...
575  */
576
577  if ((attr = ppdFindAttr(ppd, "cupsLanguages", NULL)) == NULL || !attr->value)
578    return (NULL);
579
580 /*
581  * Yes, load the list...
582  */
583
584  if ((languages = cupsArrayNew((cups_array_func_t)strcmp, NULL)) == NULL)
585    return (NULL);
586
587  if ((value = strdup(attr->value)) == NULL)
588  {
589    cupsArrayDelete(languages);
590    return (NULL);
591  }
592
593  for (ptr = value; *ptr;)
594  {
595   /*
596    * Skip leading whitespace...
597    */
598
599    while (_cups_isspace(*ptr))
600      ptr ++;
601
602    if (!*ptr)
603      break;
604
605   /*
606    * Find the end of this language name...
607    */
608
609    for (start = ptr; *ptr && !_cups_isspace(*ptr); ptr ++);
610
611    if (*ptr)
612      *ptr++ = '\0';
613
614    if (!strcmp(start, "en"))
615      continue;
616
617    cupsArrayAdd(languages, strdup(start));
618  }
619
620 /*
621  * Free the temporary string and return either an array with one or more
622  * values or a NULL pointer...
623  */
624
625  free(value);
626
627  if (cupsArrayCount(languages) == 0)
628  {
629    cupsArrayDelete(languages);
630    return (NULL);
631  }
632  else
633    return (languages);
634}
635
636
637/*
638 * '_ppdHashName()' - Generate a hash value for a device or profile name.
639 *
640 * This function is primarily used on OS X, but is generally accessible
641 * since cupstestppd needs to check for profile name collisions in PPD files...
642 */
643
644unsigned				/* O - Hash value */
645_ppdHashName(const char *name)		/* I - Name to hash */
646{
647  int		mult;			/* Multiplier */
648  unsigned	hash = 0;		/* Hash value */
649
650
651  for (mult = 1; *name && mult <= 128; mult ++, name ++)
652    hash += (*name & 255) * mult;
653
654  return (hash);
655}
656
657
658/*
659 * '_ppdLocalizedAttr()' - Find a localized attribute.
660 */
661
662ppd_attr_t *				/* O - Localized attribute or NULL */
663_ppdLocalizedAttr(ppd_file_t *ppd,	/* I - PPD file */
664		  const char *keyword,	/* I - Main keyword */
665		  const char *spec,	/* I - Option keyword */
666		  const char *ll_CC)	/* I - Language + country locale */
667{
668  char		lkeyword[PPD_MAX_NAME];	/* Localization keyword */
669  ppd_attr_t	*attr;			/* Current attribute */
670
671
672  DEBUG_printf(("4_ppdLocalizedAttr(ppd=%p, keyword=\"%s\", spec=\"%s\", "
673                "ll_CC=\"%s\")", ppd, keyword, spec, ll_CC));
674
675 /*
676  * Look for Keyword.ll_CC, then Keyword.ll...
677  */
678
679  snprintf(lkeyword, sizeof(lkeyword), "%s.%s", ll_CC, keyword);
680  if ((attr = ppdFindAttr(ppd, lkeyword, spec)) == NULL)
681  {
682    snprintf(lkeyword, sizeof(lkeyword), "%2.2s.%s", ll_CC, keyword);
683    attr = ppdFindAttr(ppd, lkeyword, spec);
684
685    if (!attr)
686    {
687      if (!strncmp(ll_CC, "ja", 2))
688      {
689       /*
690	* Due to a bug in the CUPS DDK 1.1.0 ppdmerge program, Japanese
691	* PPD files were incorrectly assigned "jp" as the locale name
692	* instead of "ja".  Support both the old (incorrect) and new
693	* locale names for Japanese...
694	*/
695
696	snprintf(lkeyword, sizeof(lkeyword), "jp.%s", keyword);
697	attr = ppdFindAttr(ppd, lkeyword, spec);
698      }
699      else if (!strncmp(ll_CC, "no", 2))
700      {
701       /*
702	* Norway has two languages, "Bokmal" (the primary one)
703	* and "Nynorsk" (new Norwegian); we map "no" to "nb" here as
704	* recommended by the locale folks...
705	*/
706
707	snprintf(lkeyword, sizeof(lkeyword), "nb.%s", keyword);
708	attr = ppdFindAttr(ppd, lkeyword, spec);
709      }
710    }
711  }
712
713#ifdef DEBUG
714  if (attr)
715    DEBUG_printf(("5_ppdLocalizedAttr: *%s %s/%s: \"%s\"\n", attr->name,
716                  attr->spec, attr->text, attr->value ? attr->value : ""));
717  else
718    DEBUG_puts("5_ppdLocalizedAttr: NOT FOUND");
719#endif /* DEBUG */
720
721  return (attr);
722}
723
724
725/*
726 * 'ppd_ll_CC()' - Get the current locale names.
727 */
728
729static cups_lang_t *			/* O - Current language */
730ppd_ll_CC(char *ll_CC,			/* O - Country-specific locale name */
731          int  ll_CC_size)		/* I - Size of country-specific name */
732{
733  cups_lang_t	*lang;			/* Current language */
734
735
736 /*
737  * Get the current locale...
738  */
739
740  if ((lang = cupsLangDefault()) == NULL)
741  {
742    strlcpy(ll_CC, "en_US", ll_CC_size);
743    return (NULL);
744  }
745
746 /*
747  * Copy the locale name...
748  */
749
750  strlcpy(ll_CC, lang->language, ll_CC_size);
751
752  if (strlen(ll_CC) == 2)
753  {
754   /*
755    * Map "ll" to primary/origin country locales to have the best
756    * chance of finding a match...
757    */
758
759    if (!strcmp(ll_CC, "cs"))
760      strlcpy(ll_CC, "cs_CZ", ll_CC_size);
761    else if (!strcmp(ll_CC, "en"))
762      strlcpy(ll_CC, "en_US", ll_CC_size);
763    else if (!strcmp(ll_CC, "ja"))
764      strlcpy(ll_CC, "ja_JP", ll_CC_size);
765    else if (!strcmp(ll_CC, "sv"))
766      strlcpy(ll_CC, "sv_SE", ll_CC_size);
767    else if (!strcmp(ll_CC, "zh"))	/* Simplified Chinese */
768      strlcpy(ll_CC, "zh_CN", ll_CC_size);
769  }
770
771  DEBUG_printf(("8ppd_ll_CC: lang->language=\"%s\", ll_CC=\"%s\"...",
772                lang->language, ll_CC));
773  return (lang);
774}
775
776
777/*
778 * End of "$Id: localize.c 11093 2013-07-03 20:48:42Z msweet $".
779 */
780