1 /* 2 * PPD localization routines for CUPS. 3 * 4 * Copyright 2007-2017 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 */ 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 */ 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 */ 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"))) 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 */ 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 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 */ 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 */ 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 */ 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 */ 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