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