]> git.ipfire.org Git - thirdparty/cups.git/blame - cups/localize.c
Merge changes from CUPS 1.4svn-r7582.
[thirdparty/cups.git] / cups / localize.c
CommitLineData
fa73b229 1/*
a603edef 2 * "$Id: localize.c 7363 2008-03-03 22:19:24Z mike $"
fa73b229 3 *
5a738aea 4 * PPD localization routines for the Common UNIX Printing System (CUPS).
fa73b229 5 *
5a738aea 6 * Copyright 2007-2008 by Apple Inc.
7594b224 7 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
fa73b229 8 *
9 * These coded instructions, statements, and computer programs are the
bc44d920 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/".
fa73b229 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 *
5a738aea
MS
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.
a603edef
MS
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.
5a738aea 38 * ppd_ll_CC() - Get the current locale names.
fa73b229 39 */
40
41/*
42 * Include necessary headers.
43 */
44
45#include "globals.h"
a603edef 46#include "ppd-private.h"
fa73b229 47#include "debug.h"
48
49
50/*
51 * Local functions...
52 */
53
a603edef 54static void ppd_ll_CC(char *ll_CC, int ll_CC_size);
fa73b229 55
56
57/*
58 * 'ppdLocalize()' - Localize the PPD file to the current locale.
89d46774 59 *
bc44d920 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 *
89d46774 64 * @since CUPS 1.2@
fa73b229 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 */
bc44d920 76 ppd_attr_t *attr, /* Current attribute */
77 *locattr; /* Localized attribute */
fa73b229 78 char ckeyword[PPD_MAX_NAME], /* Custom keyword */
a603edef 79 ll_CC[6]; /* Language + country locale */
fa73b229 80
81
82 /*
83 * Range check input...
84 */
85
d09495fa 86 DEBUG_printf(("ppdLocalize(ppd=%p)\n", ppd));
87
fa73b229 88 if (!ppd)
89 return (-1);
90
91 /*
92 * Get the default language...
93 */
94
a603edef 95 ppd_ll_CC(ll_CC, sizeof(ll_CC));
d09495fa 96
fa73b229 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 {
a603edef
MS
103 if ((locattr = _ppdLocalizedAttr(ppd, "Translation", group->name,
104 ll_CC)) != NULL)
bc44d920 105 strlcpy(group->text, locattr->text, sizeof(group->text));
fa73b229 106
107 for (j = group->num_options, option = group->options; j > 0; j --, option ++)
108 {
a603edef
MS
109 if ((locattr = _ppdLocalizedAttr(ppd, "Translation", option->keyword,
110 ll_CC)) != NULL)
bc44d920 111 strlcpy(option->text, locattr->text, sizeof(option->text));
fa73b229 112
113 for (k = option->num_choices, choice = option->choices;
114 k > 0;
115 k --, choice ++)
116 {
117 if (strcmp(choice->choice, "Custom"))
a603edef
MS
118 locattr = _ppdLocalizedAttr(ppd, option->keyword, choice->choice,
119 ll_CC);
fa73b229 120 else
121 {
122 snprintf(ckeyword, sizeof(ckeyword), "Custom%s", option->keyword);
123
a603edef 124 locattr = _ppdLocalizedAttr(ppd, ckeyword, "True", ll_CC);
fa73b229 125 }
126
bc44d920 127 if (locattr)
128 strlcpy(choice->text, locattr->text, sizeof(choice->text));
fa73b229 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
a603edef
MS
147 if ((locattr = _ppdLocalizedAttr(ppd, ckeyword, cparam->name,
148 ll_CC)) != NULL)
bc44d920 149 strlcpy(cparam->text, locattr->text, sizeof(cparam->text));
fa73b229 150 }
151 }
152
f42414bf 153 /*
154 * Translate ICC profile names...
155 */
156
157 if ((attr = ppdFindAttr(ppd, "APCustomColorMatchingName", NULL)) != NULL)
158 {
a603edef
MS
159 if ((locattr = _ppdLocalizedAttr(ppd, "APCustomColorMatchingName",
160 attr->spec, ll_CC)) != NULL)
bc44d920 161 strlcpy(attr->text, locattr->text, sizeof(attr->text));
f42414bf 162 }
163
164 for (attr = ppdFindAttr(ppd, "cupsICCProfile", NULL);
165 attr;
166 attr = ppdFindNextAttr(ppd, "cupsICCProfile", NULL))
167 {
168 cupsArraySave(ppd->sorted_attrs);
169
a603edef
MS
170 if ((locattr = _ppdLocalizedAttr(ppd, "cupsICCProfile", attr->spec,
171 ll_CC)) != NULL)
bc44d920 172 strlcpy(attr->text, locattr->text, sizeof(attr->text));
f42414bf 173
174 cupsArrayRestore(ppd->sorted_attrs);
175 }
176
09a101d6 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
a603edef
MS
187 if ((locattr = _ppdLocalizedAttr(ppd, "APPrinterPreset", attr->spec,
188 ll_CC)) != NULL)
bc44d920 189 strlcpy(attr->text, locattr->text, sizeof(attr->text));
09a101d6 190
191 cupsArrayRestore(ppd->sorted_attrs);
192 }
193
fa73b229 194 return (0);
195}
196
197
198/*
bc44d920 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
212const char * /* O - Value or NULL if not found */
213ppdLocalizeIPPReason(
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 */
bc44d920 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
a603edef 244 ppd_ll_CC(ll_CC, sizeof(ll_CC));
bc44d920 245
246 /*
247 * Find the localized attribute...
248 */
249
a603edef
MS
250 if ((locattr = _ppdLocalizedAttr(ppd, "cupsIPPReason", reason,
251 ll_CC)) == NULL)
bc44d920 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
bc44d920 280 valptr += 5;
281
282 while (*valptr && !isspace(*valptr & 255) && bufptr < bufend)
283 {
284 if (*valptr == '%' && isxdigit(valptr[1] & 255) &&
285 isxdigit(valptr[2] & 255))
286 {
287 /*
288 * Pull a hex-encoded character from the URI...
289 */
290
291 valptr ++;
292
293 if (isdigit(*valptr & 255))
294 ch = (*valptr - '0') << 4;
295 else
296 ch = (tolower(*valptr) - 'a' + 10) << 4;
297 valptr ++;
298
299 if (isdigit(*valptr & 255))
300 *bufptr++ = ch | (*valptr - '0');
301 else
302 *bufptr++ = ch | (tolower(*valptr) - 'a' + 10);
303 valptr ++;
304 }
305 else if (*valptr == '+')
306 {
307 *bufptr++ = ' ';
308 valptr ++;
309 }
310 else
311 *bufptr++ = *valptr++;
312 }
313 }
314 else
315 {
316 /*
317 * Skip this URI...
318 */
319
320 while (*valptr && !isspace(*valptr & 255))
321 valptr++;
322 }
323
324 /*
325 * Skip whitespace...
326 */
327
328 while (isspace(*valptr & 255))
329 valptr ++;
330 }
331
332 if (bufptr > buffer)
333 *bufptr = '\0';
334
335 return (buffer);
336 }
337 else
338 {
339 /*
340 * Copy a URI...
341 */
342
343 schemelen = strlen(scheme);
344 if (scheme[schemelen - 1] == ':') /* Force scheme to be just the name */
345 schemelen --;
346
347 for (valptr = locattr->value, bufptr = buffer; *valptr && bufptr < bufend;)
348 {
349 if ((!strncmp(valptr, scheme, schemelen) && valptr[schemelen] == ':') ||
350 (*valptr == '/' && !strcmp(scheme, "file")))
351 {
352 /*
353 * Copy URI...
354 */
355
356 while (*valptr && !isspace(*valptr & 255) && bufptr < bufend)
357 *bufptr++ = *valptr++;
358
359 *bufptr = '\0';
360
361 return (buffer);
362 }
363 else
364 {
365 /*
366 * Skip this URI...
367 */
368
369 while (*valptr && !isspace(*valptr & 255))
370 valptr++;
371 }
372
373 /*
374 * Skip whitespace...
375 */
376
377 while (isspace(*valptr & 255))
378 valptr ++;
379 }
380
381 return (NULL);
382 }
383}
384
385
5a738aea
MS
386/*
387 * 'ppdLocalizeMarkerName()' - Get the localized version of a marker-names
388 * attribute value.
389 *
390 * This function uses the current locale to find the corresponding name
391 * text from the attribute value. If no localized text for the requested
392 * name can be found, @code NULL@ is returned.
393 *
394 * @since CUPS 1.4@
395 */
396
397const char * /* O - Value or @code NULL@ if not found */
398ppdLocalizeMarkerName(
399 ppd_file_t *ppd, /* I - PPD file */
400 const char *name) /* I - Marker name to look up */
401{
402 ppd_attr_t *locattr; /* Localized attribute */
a603edef 403 char ll_CC[6]; /* Language + country locale */
5a738aea
MS
404
405
406 /*
407 * Range check input...
408 */
409
410 if (!ppd || !name)
411 return (NULL);
412
413 /*
414 * Get the default language...
415 */
416
a603edef 417 ppd_ll_CC(ll_CC, sizeof(ll_CC));
5a738aea
MS
418
419 /*
420 * Find the localized attribute...
421 */
422
a603edef
MS
423 if ((locattr = _ppdLocalizedAttr(ppd, "cupsMarkerName", name,
424 ll_CC)) == NULL)
5a738aea
MS
425 locattr = ppdFindAttr(ppd, "cupsMarkerName", name);
426
427 return (locattr ? locattr->text : NULL);
428}
429
430
bc44d920 431/*
a603edef 432 * '_ppdFreeLanguages()' - Free an array of languages from _ppdGetLanguages.
bc44d920 433 */
434
a603edef
MS
435void
436_ppdFreeLanguages(
437 cups_array_t *languages) /* I - Languages array */
bc44d920 438{
a603edef
MS
439 char *language; /* Current language */
440
441
442 for (language = (char *)cupsArrayFirst(languages);
443 language;
444 language = (char *)cupsArrayNext(languages))
445 free(language);
446
447 cupsArrayDelete(languages);
448}
449
450
451/*
452 * '_ppdGetLanguages()' - Get an array of languages from a PPD file.
453 */
454
455cups_array_t * /* O - Languages array */
456_ppdGetLanguages(ppd_file_t *ppd) /* I - PPD file */
457{
458 cups_array_t *languages; /* Languages array */
459 ppd_attr_t *attr; /* cupsLanguages attribute */
460 char *value, /* Copy of attribute value */
461 *start, /* Start of current language */
462 *ptr; /* Pointer into languages */
bc44d920 463
464
465 /*
a603edef 466 * See if we have a cupsLanguages attribute...
bc44d920 467 */
468
a603edef
MS
469 if ((attr = ppdFindAttr(ppd, "cupsLanguages", NULL)) == NULL || !attr->value)
470 return (NULL);
bc44d920 471
472 /*
a603edef 473 * Yes, load the list...
bc44d920 474 */
475
a603edef
MS
476 if ((languages = cupsArrayNew((cups_array_func_t)strcmp, NULL)) == NULL)
477 return (NULL);
db1f069b 478
a603edef
MS
479 if ((value = strdup(attr->value)) == NULL)
480 {
481 cupsArrayDelete(languages);
482 return (NULL);
483 }
bc44d920 484
a603edef 485 for (ptr = value; *ptr;)
bc44d920 486 {
487 /*
a603edef 488 * Skip leading whitespace...
bc44d920 489 */
490
a603edef
MS
491 while (isspace(*ptr & 255))
492 ptr ++;
493
494 if (!*ptr)
495 break;
496
497 /*
498 * Find the end of this language name...
499 */
500
501 for (start = ptr; *ptr && !isspace(*ptr & 255); ptr ++);
502
503 if (*ptr)
504 *ptr++ = '\0';
505
506 if (!strcmp(start, "en"))
507 continue;
508
509 cupsArrayAdd(languages, strdup(start));
510 }
511
512 /*
513 * Free the temporary string and return either an array with one or more
514 * values or a NULL pointer...
515 */
516
517 free(value);
518
519 if (cupsArrayCount(languages) == 0)
520 {
521 cupsArrayDelete(languages);
522 return (NULL);
bc44d920 523 }
a603edef
MS
524 else
525 return (languages);
526}
527
528
529/*
530 * '_ppdHashName()' - Generate a hash value for a device or profile name.
531 *
532 * This function is primarily used on Mac OS X, but is generally accessible
533 * since cupstestppd needs to check for profile name collisions in PPD files...
534 */
535
536unsigned /* O - Hash value */
537_ppdHashName(const char *name) /* I - Name to hash */
538{
539 int mult; /* Multiplier */
540 unsigned hash = 0; /* Hash value */
541
bc44d920 542
a603edef
MS
543 for (mult = 1; *name && mult <= 128; mult ++, name ++)
544 hash += (*name & 255) * mult;
545
546 return (hash);
bc44d920 547}
548
549
550/*
a603edef 551 * '_ppdLocalizedAttr()' - Find a localized attribute.
fa73b229 552 */
553
a603edef
MS
554ppd_attr_t * /* O - Localized attribute or NULL */
555_ppdLocalizedAttr(ppd_file_t *ppd, /* I - PPD file */
556 const char *keyword, /* I - Main keyword */
557 const char *spec, /* I - Option keyword */
558 const char *ll_CC) /* I - Language + country locale */
fa73b229 559{
560 char lkeyword[PPD_MAX_NAME]; /* Localization keyword */
561 ppd_attr_t *attr; /* Current attribute */
562
563
a603edef
MS
564 DEBUG_printf(("_ppdLocalizedAttr(ppd=%p, keyword=\"%s\", spec=\"%s\", "
565 "ll_CC=\"%s\")\n", ppd, keyword, spec, ll_CC));
d09495fa 566
fa73b229 567 /*
568 * Look for Keyword.ll_CC, then Keyword.ll...
569 */
570
d09495fa 571 snprintf(lkeyword, sizeof(lkeyword), "%s.%s", ll_CC, keyword);
fa73b229 572 if ((attr = ppdFindAttr(ppd, lkeyword, spec)) == NULL)
573 {
c9fc04c6 574 snprintf(lkeyword, sizeof(lkeyword), "%2.2s.%s", ll_CC, keyword);
fa73b229 575 attr = ppdFindAttr(ppd, lkeyword, spec);
7594b224 576
db1f069b 577 if (!attr)
7594b224 578 {
a603edef 579 if (!strncmp(ll_CC, "ja", 2))
db1f069b
MS
580 {
581 /*
582 * Due to a bug in the CUPS DDK 1.1.0 ppdmerge program, Japanese
583 * PPD files were incorrectly assigned "jp" as the locale name
584 * instead of "ja". Support both the old (incorrect) and new
585 * locale names for Japanese...
586 */
7594b224 587
db1f069b
MS
588 snprintf(lkeyword, sizeof(lkeyword), "jp.%s", keyword);
589 attr = ppdFindAttr(ppd, lkeyword, spec);
590 }
a603edef 591 else if (!strncmp(ll_CC, "no", 2))
db1f069b
MS
592 {
593 /*
594 * Norway has two languages, "Bokmal" (the primary one)
595 * and "Nynorsk" (new Norwegian); we map "no" to "nb" here as
596 * recommended by the locale folks...
597 */
598
599 snprintf(lkeyword, sizeof(lkeyword), "nb.%s", keyword);
600 attr = ppdFindAttr(ppd, lkeyword, spec);
601 }
7594b224 602 }
fa73b229 603 }
604
d09495fa 605#ifdef DEBUG
606 if (attr)
ae71f5de
MS
607 DEBUG_printf(("_ppdLocalizedAttr: *%s %s/%s: \"%s\"\n", attr->name,
608 attr->spec, attr->text, attr->value ? attr->value : ""));
d09495fa 609 else
ae71f5de 610 DEBUG_puts("_ppdLocalizedAttr: NOT FOUND");
d09495fa 611#endif /* DEBUG */
612
bc44d920 613 return (attr);
fa73b229 614}
615
616
617/*
a603edef
MS
618 * 'ppd_ll_CC()' - Get the current locale names.
619 */
620
621static void
622ppd_ll_CC(char *ll_CC, /* O - Country-specific locale name */
623 int ll_CC_size) /* I - Size of country-specific name */
624{
625 cups_lang_t *lang; /* Current language */
626
627
628 /*
629 * Get the current locale...
630 */
631
632 if ((lang = cupsLangDefault()) == NULL)
633 {
634 strlcpy(ll_CC, "en_US", ll_CC_size);
635 return;
636 }
637
638 /*
639 * Copy the locale name...
640 */
641
642 strlcpy(ll_CC, lang->language, ll_CC_size);
643
644 if (strlen(ll_CC) == 2)
645 {
646 /*
647 * Map "ll" to primary/origin country locales to have the best
648 * chance of finding a match...
649 */
650
651 if (!strcmp(ll_CC, "cs"))
652 strlcpy(ll_CC, "cs_CZ", ll_CC_size);
653 else if (!strcmp(ll_CC, "en"))
654 strlcpy(ll_CC, "en_US", ll_CC_size);
655 else if (!strcmp(ll_CC, "ja"))
656 strlcpy(ll_CC, "ja_JP", ll_CC_size);
657 else if (!strcmp(ll_CC, "sv"))
658 strlcpy(ll_CC, "sv_SE", ll_CC_size);
659 else if (!strcmp(ll_CC, "zh")) /* Simplified Chinese */
660 strlcpy(ll_CC, "zh_CN", ll_CC_size);
661 else if (ll_CC_size >= 6)
662 {
663 ll_CC[2] = '_';
664 ll_CC[3] = toupper(ll_CC[0] & 255);
665 ll_CC[4] = toupper(ll_CC[1] & 255);
666 ll_CC[5] = '\0';
667 }
668 }
669
670 DEBUG_printf(("ppd_ll_CC: lang->language=\"%s\", ll_CC=\"%s\"...\n",
671 lang->language, ll_CC));
672}
673
674
675/*
676 * End of "$Id: localize.c 7363 2008-03-03 22:19:24Z mike $".
fa73b229 677 */