]> git.ipfire.org Git - thirdparty/cups.git/blob - cups/localize.c
Merge changes from CUPS 1.4svn-r7696.
[thirdparty/cups.git] / cups / localize.c
1 /*
2 * "$Id: localize.c 7679 2008-06-19 23:37:45Z mike $"
3 *
4 * PPD localization routines for the Common UNIX Printing System (CUPS).
5 *
6 * Copyright 2007-2008 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 "globals.h"
47 #include "ppd-private.h"
48 #include "debug.h"
49
50
51 /*
52 * Local functions...
53 */
54
55 static void ppd_ll_CC(char *ll_CC, int ll_CC_size);
56
57
58 /*
59 * 'ppdLocalize()' - Localize the PPD file to the current locale.
60 *
61 * All groups, options, and choices are localized, as are ICC profile
62 * descriptions, printer presets, and custom option parameters. Each
63 * localized string uses the UTF-8 character encoding.
64 *
65 * @since CUPS 1.2@
66 */
67
68 int /* O - 0 on success, -1 on error */
69 ppdLocalize(ppd_file_t *ppd) /* I - PPD file */
70 {
71 int i, j, k; /* Looping vars */
72 ppd_group_t *group; /* Current group */
73 ppd_option_t *option; /* Current option */
74 ppd_choice_t *choice; /* Current choice */
75 ppd_coption_t *coption; /* Current custom option */
76 ppd_cparam_t *cparam; /* Current custom parameter */
77 ppd_attr_t *attr, /* Current attribute */
78 *locattr; /* Localized attribute */
79 char ckeyword[PPD_MAX_NAME], /* Custom keyword */
80 ll_CC[6]; /* Language + country locale */
81
82
83 /*
84 * Range check input...
85 */
86
87 DEBUG_printf(("ppdLocalize(ppd=%p)\n", ppd));
88
89 if (!ppd)
90 return (-1);
91
92 /*
93 * Get the default language...
94 */
95
96 ppd_ll_CC(ll_CC, sizeof(ll_CC));
97
98 /*
99 * Now lookup all of the groups, options, choices, etc.
100 */
101
102 for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++)
103 {
104 if ((locattr = _ppdLocalizedAttr(ppd, "Translation", group->name,
105 ll_CC)) != NULL)
106 strlcpy(group->text, locattr->text, sizeof(group->text));
107
108 for (j = group->num_options, option = group->options; j > 0; j --, option ++)
109 {
110 if ((locattr = _ppdLocalizedAttr(ppd, "Translation", option->keyword,
111 ll_CC)) != NULL)
112 strlcpy(option->text, locattr->text, sizeof(option->text));
113
114 for (k = option->num_choices, choice = option->choices;
115 k > 0;
116 k --, choice ++)
117 {
118 if (strcmp(choice->choice, "Custom"))
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
207 ppd_attr_t * /* O - Localized attribute or @code NULL@ if none exists */
208 ppdLocalizeAttr(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@
250 */
251
252 const char * /* O - Value or NULL if not found */
253 ppdLocalizeIPPReason(
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 ppd_attr_t *locattr; /* Localized attribute */
261 char ll_CC[6], /* Language + country locale */
262 *bufptr, /* Pointer into buffer */
263 *bufend, /* Pointer to end of buffer */
264 *valptr; /* Pointer into value */
265 int ch, /* Hex-encoded character */
266 schemelen; /* Length of scheme name */
267
268
269 /*
270 * Range check input...
271 */
272
273 if (buffer)
274 *buffer = '\0';
275
276 if (!ppd || !reason || (scheme && !*scheme) ||
277 !buffer || bufsize < PPD_MAX_TEXT)
278 return (NULL);
279
280 /*
281 * Get the default language...
282 */
283
284 ppd_ll_CC(ll_CC, sizeof(ll_CC));
285
286 /*
287 * Find the localized attribute...
288 */
289
290 if ((locattr = _ppdLocalizedAttr(ppd, "cupsIPPReason", reason,
291 ll_CC)) == NULL)
292 locattr = ppdFindAttr(ppd, "cupsIPPReason", reason);
293
294 if (!locattr)
295 return (NULL);
296
297 /*
298 * Now find the value we need...
299 */
300
301 bufend = buffer + bufsize - 1;
302
303 if (!scheme || !strcmp(scheme, "text"))
304 {
305 /*
306 * Copy a text value (either the translation text or text:... URIs from
307 * the value...
308 */
309
310 strlcpy(buffer, locattr->text, bufsize);
311
312 for (valptr = locattr->value, bufptr = buffer; *valptr && bufptr < bufend;)
313 {
314 if (!strncmp(valptr, "text:", 5))
315 {
316 /*
317 * Decode text: URI and add to the buffer...
318 */
319
320 valptr += 5;
321
322 while (*valptr && !isspace(*valptr & 255) && bufptr < bufend)
323 {
324 if (*valptr == '%' && isxdigit(valptr[1] & 255) &&
325 isxdigit(valptr[2] & 255))
326 {
327 /*
328 * Pull a hex-encoded character from the URI...
329 */
330
331 valptr ++;
332
333 if (isdigit(*valptr & 255))
334 ch = (*valptr - '0') << 4;
335 else
336 ch = (tolower(*valptr) - 'a' + 10) << 4;
337 valptr ++;
338
339 if (isdigit(*valptr & 255))
340 *bufptr++ = ch | (*valptr - '0');
341 else
342 *bufptr++ = ch | (tolower(*valptr) - 'a' + 10);
343 valptr ++;
344 }
345 else if (*valptr == '+')
346 {
347 *bufptr++ = ' ';
348 valptr ++;
349 }
350 else
351 *bufptr++ = *valptr++;
352 }
353 }
354 else
355 {
356 /*
357 * Skip this URI...
358 */
359
360 while (*valptr && !isspace(*valptr & 255))
361 valptr++;
362 }
363
364 /*
365 * Skip whitespace...
366 */
367
368 while (isspace(*valptr & 255))
369 valptr ++;
370 }
371
372 if (bufptr > buffer)
373 *bufptr = '\0';
374
375 return (buffer);
376 }
377 else
378 {
379 /*
380 * Copy a URI...
381 */
382
383 schemelen = strlen(scheme);
384 if (scheme[schemelen - 1] == ':') /* Force scheme to be just the name */
385 schemelen --;
386
387 for (valptr = locattr->value, bufptr = buffer; *valptr && bufptr < bufend;)
388 {
389 if ((!strncmp(valptr, scheme, schemelen) && valptr[schemelen] == ':') ||
390 (*valptr == '/' && !strcmp(scheme, "file")))
391 {
392 /*
393 * Copy URI...
394 */
395
396 while (*valptr && !isspace(*valptr & 255) && bufptr < bufend)
397 *bufptr++ = *valptr++;
398
399 *bufptr = '\0';
400
401 return (buffer);
402 }
403 else
404 {
405 /*
406 * Skip this URI...
407 */
408
409 while (*valptr && !isspace(*valptr & 255))
410 valptr++;
411 }
412
413 /*
414 * Skip whitespace...
415 */
416
417 while (isspace(*valptr & 255))
418 valptr ++;
419 }
420
421 return (NULL);
422 }
423 }
424
425
426 /*
427 * 'ppdLocalizeMarkerName()' - Get the localized version of a marker-names
428 * attribute value.
429 *
430 * This function uses the current locale to find the corresponding name
431 * text from the attribute value. If no localized text for the requested
432 * name can be found, @code NULL@ is returned.
433 *
434 * @since CUPS 1.4@
435 */
436
437 const char * /* O - Value or @code NULL@ if not found */
438 ppdLocalizeMarkerName(
439 ppd_file_t *ppd, /* I - PPD file */
440 const char *name) /* I - Marker name to look up */
441 {
442 ppd_attr_t *locattr; /* Localized attribute */
443 char ll_CC[6]; /* Language + country locale */
444
445
446 /*
447 * Range check input...
448 */
449
450 if (!ppd || !name)
451 return (NULL);
452
453 /*
454 * Get the default language...
455 */
456
457 ppd_ll_CC(ll_CC, sizeof(ll_CC));
458
459 /*
460 * Find the localized attribute...
461 */
462
463 if ((locattr = _ppdLocalizedAttr(ppd, "cupsMarkerName", name,
464 ll_CC)) == NULL)
465 locattr = ppdFindAttr(ppd, "cupsMarkerName", name);
466
467 return (locattr ? locattr->text : NULL);
468 }
469
470
471 /*
472 * '_ppdFreeLanguages()' - Free an array of languages from _ppdGetLanguages.
473 */
474
475 void
476 _ppdFreeLanguages(
477 cups_array_t *languages) /* I - Languages array */
478 {
479 char *language; /* Current language */
480
481
482 for (language = (char *)cupsArrayFirst(languages);
483 language;
484 language = (char *)cupsArrayNext(languages))
485 free(language);
486
487 cupsArrayDelete(languages);
488 }
489
490
491 /*
492 * '_ppdGetLanguages()' - Get an array of languages from a PPD file.
493 */
494
495 cups_array_t * /* O - Languages array */
496 _ppdGetLanguages(ppd_file_t *ppd) /* I - PPD file */
497 {
498 cups_array_t *languages; /* Languages array */
499 ppd_attr_t *attr; /* cupsLanguages attribute */
500 char *value, /* Copy of attribute value */
501 *start, /* Start of current language */
502 *ptr; /* Pointer into languages */
503
504
505 /*
506 * See if we have a cupsLanguages attribute...
507 */
508
509 if ((attr = ppdFindAttr(ppd, "cupsLanguages", NULL)) == NULL || !attr->value)
510 return (NULL);
511
512 /*
513 * Yes, load the list...
514 */
515
516 if ((languages = cupsArrayNew((cups_array_func_t)strcmp, NULL)) == NULL)
517 return (NULL);
518
519 if ((value = strdup(attr->value)) == NULL)
520 {
521 cupsArrayDelete(languages);
522 return (NULL);
523 }
524
525 for (ptr = value; *ptr;)
526 {
527 /*
528 * Skip leading whitespace...
529 */
530
531 while (isspace(*ptr & 255))
532 ptr ++;
533
534 if (!*ptr)
535 break;
536
537 /*
538 * Find the end of this language name...
539 */
540
541 for (start = ptr; *ptr && !isspace(*ptr & 255); ptr ++);
542
543 if (*ptr)
544 *ptr++ = '\0';
545
546 if (!strcmp(start, "en"))
547 continue;
548
549 cupsArrayAdd(languages, strdup(start));
550 }
551
552 /*
553 * Free the temporary string and return either an array with one or more
554 * values or a NULL pointer...
555 */
556
557 free(value);
558
559 if (cupsArrayCount(languages) == 0)
560 {
561 cupsArrayDelete(languages);
562 return (NULL);
563 }
564 else
565 return (languages);
566 }
567
568
569 /*
570 * '_ppdHashName()' - Generate a hash value for a device or profile name.
571 *
572 * This function is primarily used on Mac OS X, but is generally accessible
573 * since cupstestppd needs to check for profile name collisions in PPD files...
574 */
575
576 unsigned /* O - Hash value */
577 _ppdHashName(const char *name) /* I - Name to hash */
578 {
579 int mult; /* Multiplier */
580 unsigned hash = 0; /* Hash value */
581
582
583 for (mult = 1; *name && mult <= 128; mult ++, name ++)
584 hash += (*name & 255) * mult;
585
586 return (hash);
587 }
588
589
590 /*
591 * '_ppdLocalizedAttr()' - Find a localized attribute.
592 */
593
594 ppd_attr_t * /* O - Localized attribute or NULL */
595 _ppdLocalizedAttr(ppd_file_t *ppd, /* I - PPD file */
596 const char *keyword, /* I - Main keyword */
597 const char *spec, /* I - Option keyword */
598 const char *ll_CC) /* I - Language + country locale */
599 {
600 char lkeyword[PPD_MAX_NAME]; /* Localization keyword */
601 ppd_attr_t *attr; /* Current attribute */
602
603
604 DEBUG_printf(("_ppdLocalizedAttr(ppd=%p, keyword=\"%s\", spec=\"%s\", "
605 "ll_CC=\"%s\")\n", ppd, keyword, spec, ll_CC));
606
607 /*
608 * Look for Keyword.ll_CC, then Keyword.ll...
609 */
610
611 snprintf(lkeyword, sizeof(lkeyword), "%s.%s", ll_CC, keyword);
612 if ((attr = ppdFindAttr(ppd, lkeyword, spec)) == NULL)
613 {
614 snprintf(lkeyword, sizeof(lkeyword), "%2.2s.%s", ll_CC, keyword);
615 attr = ppdFindAttr(ppd, lkeyword, spec);
616
617 if (!attr)
618 {
619 if (!strncmp(ll_CC, "ja", 2))
620 {
621 /*
622 * Due to a bug in the CUPS DDK 1.1.0 ppdmerge program, Japanese
623 * PPD files were incorrectly assigned "jp" as the locale name
624 * instead of "ja". Support both the old (incorrect) and new
625 * locale names for Japanese...
626 */
627
628 snprintf(lkeyword, sizeof(lkeyword), "jp.%s", keyword);
629 attr = ppdFindAttr(ppd, lkeyword, spec);
630 }
631 else if (!strncmp(ll_CC, "no", 2))
632 {
633 /*
634 * Norway has two languages, "Bokmal" (the primary one)
635 * and "Nynorsk" (new Norwegian); we map "no" to "nb" here as
636 * recommended by the locale folks...
637 */
638
639 snprintf(lkeyword, sizeof(lkeyword), "nb.%s", keyword);
640 attr = ppdFindAttr(ppd, lkeyword, spec);
641 }
642 }
643 }
644
645 #ifdef DEBUG
646 if (attr)
647 DEBUG_printf(("_ppdLocalizedAttr: *%s %s/%s: \"%s\"\n", attr->name,
648 attr->spec, attr->text, attr->value ? attr->value : ""));
649 else
650 DEBUG_puts("_ppdLocalizedAttr: NOT FOUND");
651 #endif /* DEBUG */
652
653 return (attr);
654 }
655
656
657 /*
658 * 'ppd_ll_CC()' - Get the current locale names.
659 */
660
661 static void
662 ppd_ll_CC(char *ll_CC, /* O - Country-specific locale name */
663 int ll_CC_size) /* I - Size of country-specific name */
664 {
665 cups_lang_t *lang; /* Current language */
666
667
668 /*
669 * Get the current locale...
670 */
671
672 if ((lang = cupsLangDefault()) == NULL)
673 {
674 strlcpy(ll_CC, "en_US", ll_CC_size);
675 return;
676 }
677
678 /*
679 * Copy the locale name...
680 */
681
682 strlcpy(ll_CC, lang->language, ll_CC_size);
683
684 if (strlen(ll_CC) == 2)
685 {
686 /*
687 * Map "ll" to primary/origin country locales to have the best
688 * chance of finding a match...
689 */
690
691 if (!strcmp(ll_CC, "cs"))
692 strlcpy(ll_CC, "cs_CZ", ll_CC_size);
693 else if (!strcmp(ll_CC, "en"))
694 strlcpy(ll_CC, "en_US", ll_CC_size);
695 else if (!strcmp(ll_CC, "ja"))
696 strlcpy(ll_CC, "ja_JP", ll_CC_size);
697 else if (!strcmp(ll_CC, "sv"))
698 strlcpy(ll_CC, "sv_SE", ll_CC_size);
699 else if (!strcmp(ll_CC, "zh")) /* Simplified Chinese */
700 strlcpy(ll_CC, "zh_CN", ll_CC_size);
701 }
702
703 DEBUG_printf(("ppd_ll_CC: lang->language=\"%s\", ll_CC=\"%s\"...\n",
704 lang->language, ll_CC));
705 }
706
707
708 /*
709 * End of "$Id: localize.c 7679 2008-06-19 23:37:45Z mike $".
710 */