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