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