1 /* 2 * I18N/language support for CUPS. 3 * 4 * Copyright 2007-2017 by Apple Inc. 5 * Copyright 1997-2007 by Easy Software Products. 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 * This file is subject to the Apple OS-Developed Software exception. 14 */ 15 16 /* 17 * Include necessary headers... 18 */ 19 20 #include "cups-private.h" 21 #ifdef HAVE_LANGINFO_H 22 # include <langinfo.h> 23 #endif /* HAVE_LANGINFO_H */ 24 #ifdef WIN32 25 # include <io.h> 26 #else 27 # include <unistd.h> 28 #endif /* WIN32 */ 29 #ifdef HAVE_COREFOUNDATION_H 30 # include <CoreFoundation/CoreFoundation.h> 31 #endif /* HAVE_COREFOUNDATION_H */ 32 33 34 /* 35 * Local globals... 36 */ 37 38 static _cups_mutex_t lang_mutex = _CUPS_MUTEX_INITIALIZER; 39 /* Mutex to control access to cache */ 40 static cups_lang_t *lang_cache = NULL; 41 /* Language string cache */ 42 static const char * const lang_encodings[] = 43 { /* Encoding strings */ 44 "us-ascii", "iso-8859-1", 45 "iso-8859-2", "iso-8859-3", 46 "iso-8859-4", "iso-8859-5", 47 "iso-8859-6", "iso-8859-7", 48 "iso-8859-8", "iso-8859-9", 49 "iso-8859-10", "utf-8", 50 "iso-8859-13", "iso-8859-14", 51 "iso-8859-15", "cp874", 52 "cp1250", "cp1251", 53 "cp1252", "cp1253", 54 "cp1254", "cp1255", 55 "cp1256", "cp1257", 56 "cp1258", "koi8-r", 57 "koi8-u", "iso-8859-11", 58 "iso-8859-16", "mac", 59 "unknown", "unknown", 60 "unknown", "unknown", 61 "unknown", "unknown", 62 "unknown", "unknown", 63 "unknown", "unknown", 64 "unknown", "unknown", 65 "unknown", "unknown", 66 "unknown", "unknown", 67 "unknown", "unknown", 68 "unknown", "unknown", 69 "unknown", "unknown", 70 "unknown", "unknown", 71 "unknown", "unknown", 72 "unknown", "unknown", 73 "unknown", "unknown", 74 "unknown", "unknown", 75 "unknown", "unknown", 76 "cp932", "cp936", 77 "cp949", "cp950", 78 "cp1361", "unknown", 79 "unknown", "unknown", 80 "unknown", "unknown", 81 "unknown", "unknown", 82 "unknown", "unknown", 83 "unknown", "unknown", 84 "unknown", "unknown", 85 "unknown", "unknown", 86 "unknown", "unknown", 87 "unknown", "unknown", 88 "unknown", "unknown", 89 "unknown", "unknown", 90 "unknown", "unknown", 91 "unknown", "unknown", 92 "unknown", "unknown", 93 "unknown", "unknown", 94 "unknown", "unknown", 95 "unknown", "unknown", 96 "unknown", "unknown", 97 "unknown", "unknown", 98 "unknown", "unknown", 99 "unknown", "unknown", 100 "unknown", "unknown", 101 "unknown", "unknown", 102 "unknown", "unknown", 103 "unknown", "unknown", 104 "unknown", "unknown", 105 "unknown", "unknown", 106 "unknown", "unknown", 107 "unknown", "unknown", 108 "euc-cn", "euc-jp", 109 "euc-kr", "euc-tw", 110 "shift_jisx0213" 111 }; 112 113 #ifdef __APPLE__ 114 typedef struct 115 { 116 const char * const language; /* Language ID */ 117 const char * const locale; /* Locale ID */ 118 } _apple_language_locale_t; 119 120 static const _apple_language_locale_t apple_language_locale[] = 121 { /* Language to locale ID LUT */ 122 { "en", "en_US" }, 123 { "nb", "no" }, 124 { "nb_NO", "no" }, 125 { "zh-Hans", "zh_CN" }, 126 { "zh_HANS", "zh_CN" }, 127 { "zh-Hant", "zh_TW" }, 128 { "zh_HANT", "zh_TW" }, 129 { "zh-Hant_CN", "zh_TW" } 130 }; 131 #endif /* __APPLE__ */ 132 133 134 /* 135 * Local functions... 136 */ 137 138 139 #ifdef __APPLE__ 140 static const char *appleLangDefault(void); 141 # ifdef CUPS_BUNDLEDIR 142 # ifndef CF_RETURNS_RETAINED 143 # if __has_feature(attribute_cf_returns_retained) 144 # define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) 145 # else 146 # define CF_RETURNS_RETAINED 147 # endif /* __has_feature(attribute_cf_returns_retained) */ 148 # endif /* !CF_RETURNED_RETAINED */ 149 static cups_array_t *appleMessageLoad(const char *locale) 150 CF_RETURNS_RETAINED; 151 # endif /* CUPS_BUNDLEDIR */ 152 #endif /* __APPLE__ */ 153 static cups_lang_t *cups_cache_lookup(const char *name, 154 cups_encoding_t encoding); 155 static int cups_message_compare(_cups_message_t *m1, 156 _cups_message_t *m2); 157 static void cups_message_free(_cups_message_t *m); 158 static void cups_message_load(cups_lang_t *lang); 159 static void cups_unquote(char *d, const char *s); 160 161 162 #ifdef __APPLE__ 163 /* 164 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a 165 * locale ID. 166 */ 167 168 const char * /* O - Language ID */ 169 _cupsAppleLanguage(const char *locale, /* I - Locale ID */ 170 char *language,/* I - Language ID buffer */ 171 size_t langsize) /* I - Size of language ID buffer */ 172 { 173 int i; /* Looping var */ 174 CFStringRef localeid, /* CF locale identifier */ 175 langid; /* CF language identifier */ 176 177 178 /* 179 * Copy the locale name and convert, as needed, to the Apple-specific 180 * locale identifier... 181 */ 182 183 switch (strlen(locale)) 184 { 185 default : 186 /* 187 * Invalid locale... 188 */ 189 190 strlcpy(language, "en", langsize); 191 break; 192 193 case 2 : 194 strlcpy(language, locale, langsize); 195 break; 196 197 case 5 : 198 strlcpy(language, locale, langsize); 199 200 if (language[2] == '-') 201 { 202 /* 203 * Convert ll-cc to ll_CC... 204 */ 205 206 language[2] = '_'; 207 language[3] = (char)toupper(language[3] & 255); 208 language[4] = (char)toupper(language[4] & 255); 209 } 210 break; 211 } 212 213 for (i = 0; 214 i < (int)(sizeof(apple_language_locale) / 215 sizeof(apple_language_locale[0])); 216 i ++) 217 if (!strcmp(locale, apple_language_locale[i].locale)) 218 { 219 strlcpy(language, apple_language_locale[i].language, sizeof(language)); 220 break; 221 } 222 223 /* 224 * Attempt to map the locale ID to a language ID... 225 */ 226 227 if ((localeid = CFStringCreateWithCString(kCFAllocatorDefault, language, 228 kCFStringEncodingASCII)) != NULL) 229 { 230 if ((langid = CFLocaleCreateCanonicalLanguageIdentifierFromString( 231 kCFAllocatorDefault, localeid)) != NULL) 232 { 233 CFStringGetCString(langid, language, (CFIndex)langsize, kCFStringEncodingASCII); 234 CFRelease(langid); 235 } 236 237 CFRelease(localeid); 238 } 239 240 /* 241 * Return what we got... 242 */ 243 244 return (language); 245 } 246 247 248 /* 249 * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID. 250 */ 251 252 const char * /* O - Locale */ 253 _cupsAppleLocale(CFStringRef languageName, /* I - Apple language ID */ 254 char *locale, /* I - Buffer for locale */ 255 size_t localesize) /* I - Size of buffer */ 256 { 257 int i; /* Looping var */ 258 CFStringRef localeName; /* Locale as a CF string */ 259 #ifdef DEBUG 260 char temp[1024]; /* Temporary string */ 261 262 263 if (!CFStringGetCString(languageName, temp, (CFIndex)sizeof(temp), kCFStringEncodingASCII)) 264 temp[0] = '\0'; 265 266 DEBUG_printf(("_cupsAppleLocale(languageName=%p(%s), locale=%p, localsize=%d)", (void *)languageName, temp, (void *)locale, (int)localesize)); 267 #endif /* DEBUG */ 268 269 localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, languageName); 270 271 if (localeName) 272 { 273 /* 274 * Copy the locale name and tweak as needed... 275 */ 276 277 if (!CFStringGetCString(localeName, locale, (CFIndex)localesize, kCFStringEncodingASCII)) 278 *locale = '\0'; 279 280 DEBUG_printf(("_cupsAppleLocale: locale=\"%s\"", locale)); 281 282 CFRelease(localeName); 283 284 /* 285 * Map new language identifiers to locales... 286 */ 287 288 for (i = 0; 289 i < (int)(sizeof(apple_language_locale) / 290 sizeof(apple_language_locale[0])); 291 i ++) 292 { 293 size_t len = strlen(apple_language_locale[i].language); 294 295 if (!strcmp(locale, apple_language_locale[i].language) || 296 (!strncmp(locale, apple_language_locale[i].language, len) && (locale[len] == '_' || locale[len] == '-'))) 297 { 298 DEBUG_printf(("_cupsAppleLocale: Updating locale to \"%s\".", apple_language_locale[i].locale)); 299 strlcpy(locale, apple_language_locale[i].locale, localesize); 300 break; 301 } 302 } 303 } 304 else 305 { 306 /* 307 * Just try the Apple language name... 308 */ 309 310 if (!CFStringGetCString(languageName, locale, (CFIndex)localesize, kCFStringEncodingASCII)) 311 *locale = '\0'; 312 } 313 314 if (!*locale) 315 { 316 DEBUG_puts("_cupsAppleLocale: Returning NULL."); 317 return (NULL); 318 } 319 320 /* 321 * Convert language subtag into region subtag... 322 */ 323 324 if (locale[2] == '-') 325 locale[2] = '_'; 326 else if (locale[3] == '-') 327 locale[3] = '_'; 328 329 if (!strchr(locale, '.')) 330 strlcat(locale, ".UTF-8", localesize); 331 332 DEBUG_printf(("_cupsAppleLocale: Returning \"%s\".", locale)); 333 334 return (locale); 335 } 336 #endif /* __APPLE__ */ 337 338 339 /* 340 * '_cupsEncodingName()' - Return the character encoding name string 341 * for the given encoding enumeration. 342 */ 343 344 const char * /* O - Character encoding */ 345 _cupsEncodingName( 346 cups_encoding_t encoding) /* I - Encoding value */ 347 { 348 if (encoding < CUPS_US_ASCII || 349 encoding >= (cups_encoding_t)(sizeof(lang_encodings) / sizeof(lang_encodings[0]))) 350 { 351 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")", 352 encoding, lang_encodings[0])); 353 return (lang_encodings[0]); 354 } 355 else 356 { 357 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"", 358 encoding, lang_encodings[encoding])); 359 return (lang_encodings[encoding]); 360 } 361 } 362 363 364 /* 365 * 'cupsLangDefault()' - Return the default language. 366 */ 367 368 cups_lang_t * /* O - Language data */ 369 cupsLangDefault(void) 370 { 371 return (cupsLangGet(NULL)); 372 } 373 374 375 /* 376 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.) 377 * for the given language. 378 */ 379 380 const char * /* O - Character encoding */ 381 cupsLangEncoding(cups_lang_t *lang) /* I - Language data */ 382 { 383 if (lang == NULL) 384 return ((char*)lang_encodings[0]); 385 else 386 return ((char*)lang_encodings[lang->encoding]); 387 } 388 389 390 /* 391 * 'cupsLangFlush()' - Flush all language data out of the cache. 392 */ 393 394 void 395 cupsLangFlush(void) 396 { 397 cups_lang_t *lang, /* Current language */ 398 *next; /* Next language */ 399 400 401 /* 402 * Free all languages in the cache... 403 */ 404 405 _cupsMutexLock(&lang_mutex); 406 407 for (lang = lang_cache; lang != NULL; lang = next) 408 { 409 /* 410 * Free all messages... 411 */ 412 413 _cupsMessageFree(lang->strings); 414 415 /* 416 * Then free the language structure itself... 417 */ 418 419 next = lang->next; 420 free(lang); 421 } 422 423 lang_cache = NULL; 424 425 _cupsMutexUnlock(&lang_mutex); 426 } 427 428 429 /* 430 * 'cupsLangFree()' - Free language data. 431 * 432 * This does not actually free anything; use @link cupsLangFlush@ for that. 433 */ 434 435 void 436 cupsLangFree(cups_lang_t *lang) /* I - Language to free */ 437 { 438 _cupsMutexLock(&lang_mutex); 439 440 if (lang != NULL && lang->used > 0) 441 lang->used --; 442 443 _cupsMutexUnlock(&lang_mutex); 444 } 445 446 447 /* 448 * 'cupsLangGet()' - Get a language. 449 */ 450 451 cups_lang_t * /* O - Language data */ 452 cupsLangGet(const char *language) /* I - Language or locale */ 453 { 454 int i; /* Looping var */ 455 #ifndef __APPLE__ 456 char locale[255]; /* Copy of locale name */ 457 #endif /* !__APPLE__ */ 458 char langname[16], /* Requested language name */ 459 country[16], /* Country code */ 460 charset[16], /* Character set */ 461 *csptr, /* Pointer to CODESET string */ 462 *ptr, /* Pointer into language/charset */ 463 real[48]; /* Real language name */ 464 cups_encoding_t encoding; /* Encoding to use */ 465 cups_lang_t *lang; /* Current language... */ 466 static const char * const locale_encodings[] = 467 { /* Locale charset names */ 468 "ASCII", "ISO88591", "ISO88592", "ISO88593", 469 "ISO88594", "ISO88595", "ISO88596", "ISO88597", 470 "ISO88598", "ISO88599", "ISO885910", "UTF8", 471 "ISO885913", "ISO885914", "ISO885915", "CP874", 472 "CP1250", "CP1251", "CP1252", "CP1253", 473 "CP1254", "CP1255", "CP1256", "CP1257", 474 "CP1258", "KOI8R", "KOI8U", "ISO885911", 475 "ISO885916", "MACROMAN", "", "", 476 477 "", "", "", "", 478 "", "", "", "", 479 "", "", "", "", 480 "", "", "", "", 481 "", "", "", "", 482 "", "", "", "", 483 "", "", "", "", 484 "", "", "", "", 485 486 "CP932", "CP936", "CP949", "CP950", 487 "CP1361", "", "", "", 488 "", "", "", "", 489 "", "", "", "", 490 "", "", "", "", 491 "", "", "", "", 492 "", "", "", "", 493 "", "", "", "", 494 495 "", "", "", "", 496 "", "", "", "", 497 "", "", "", "", 498 "", "", "", "", 499 "", "", "", "", 500 "", "", "", "", 501 "", "", "", "", 502 "", "", "", "", 503 504 "EUCCN", "EUCJP", "EUCKR", "EUCTW", 505 "SHIFT_JISX0213" 506 }; 507 508 509 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language)); 510 511 #ifdef __APPLE__ 512 /* 513 * Set the character set to UTF-8... 514 */ 515 516 strlcpy(charset, "UTF8", sizeof(charset)); 517 518 /* 519 * Apple's setlocale doesn't give us the user's localization 520 * preference so we have to look it up this way... 521 */ 522 523 if (!language) 524 { 525 if (!getenv("SOFTWARE") || (language = getenv("LANG")) == NULL) 526 language = appleLangDefault(); 527 528 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language)); 529 } 530 531 #else 532 /* 533 * Set the charset to "unknown"... 534 */ 535 536 charset[0] = '\0'; 537 538 /* 539 * Use setlocale() to determine the currently set locale, and then 540 * fallback to environment variables to avoid setting the locale, 541 * since setlocale() is not thread-safe! 542 */ 543 544 if (!language) 545 { 546 /* 547 * First see if the locale has been set; if it is still "C" or 548 * "POSIX", use the environment to get the default... 549 */ 550 551 # ifdef LC_MESSAGES 552 ptr = setlocale(LC_MESSAGES, NULL); 553 # else 554 ptr = setlocale(LC_ALL, NULL); 555 # endif /* LC_MESSAGES */ 556 557 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr)); 558 559 if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX")) 560 { 561 /* 562 * Get the character set from the LC_CTYPE locale setting... 563 */ 564 565 if ((ptr = getenv("LC_CTYPE")) == NULL) 566 if ((ptr = getenv("LC_ALL")) == NULL) 567 if ((ptr = getenv("LANG")) == NULL) 568 ptr = "en_US"; 569 570 if ((csptr = strchr(ptr, '.')) != NULL) 571 { 572 /* 573 * Extract the character set from the environment... 574 */ 575 576 for (ptr = charset, csptr ++; *csptr; csptr ++) 577 if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr)) 578 *ptr++ = *csptr; 579 580 *ptr = '\0'; 581 } 582 583 /* 584 * Get the locale for messages from the LC_MESSAGES locale setting... 585 */ 586 587 if ((ptr = getenv("LC_MESSAGES")) == NULL) 588 if ((ptr = getenv("LC_ALL")) == NULL) 589 if ((ptr = getenv("LANG")) == NULL) 590 ptr = "en_US"; 591 } 592 593 if (ptr) 594 { 595 strlcpy(locale, ptr, sizeof(locale)); 596 language = locale; 597 598 /* 599 * CUPS STR #2575: Map "nb" to "no" for back-compatibility... 600 */ 601 602 if (!strncmp(locale, "nb", 2)) 603 locale[1] = 'o'; 604 605 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language)); 606 } 607 } 608 #endif /* __APPLE__ */ 609 610 /* 611 * If "language" is NULL at this point, then chances are we are using 612 * a language that is not installed for the base OS. 613 */ 614 615 if (!language) 616 { 617 /* 618 * Switch to the POSIX ("C") locale... 619 */ 620 621 language = "C"; 622 } 623 624 #ifdef CODESET 625 /* 626 * On systems that support the nl_langinfo(CODESET) call, use 627 * this value as the character set... 628 */ 629 630 if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL) 631 { 632 /* 633 * Copy all of the letters and numbers in the CODESET string... 634 */ 635 636 for (ptr = charset; *csptr; csptr ++) 637 if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1)) 638 *ptr++ = *csptr; 639 640 *ptr = '\0'; 641 642 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via " 643 "nl_langinfo(CODESET)...", charset)); 644 } 645 #endif /* CODESET */ 646 647 /* 648 * If we don't have a character set by now, default to UTF-8... 649 */ 650 651 if (!charset[0]) 652 strlcpy(charset, "UTF8", sizeof(charset)); 653 654 /* 655 * Parse the language string passed in to a locale string. "C" is the 656 * standard POSIX locale and is copied unchanged. Otherwise the 657 * language string is converted from ll-cc[.charset] (language-country) 658 * to ll_CC[.CHARSET] to match the file naming convention used by all 659 * POSIX-compliant operating systems. Invalid language names are mapped 660 * to the POSIX locale. 661 */ 662 663 country[0] = '\0'; 664 665 if (language == NULL || !language[0] || 666 !strcmp(language, "POSIX")) 667 strlcpy(langname, "C", sizeof(langname)); 668 else 669 { 670 /* 671 * Copy the parts of the locale string over safely... 672 */ 673 674 for (ptr = langname; *language; language ++) 675 if (*language == '_' || *language == '-' || *language == '.') 676 break; 677 else if (ptr < (langname + sizeof(langname) - 1)) 678 *ptr++ = (char)tolower(*language & 255); 679 680 *ptr = '\0'; 681 682 if (*language == '_' || *language == '-') 683 { 684 /* 685 * Copy the country code... 686 */ 687 688 for (language ++, ptr = country; *language; language ++) 689 if (*language == '.') 690 break; 691 else if (ptr < (country + sizeof(country) - 1)) 692 *ptr++ = (char)toupper(*language & 255); 693 694 *ptr = '\0'; 695 696 /* 697 * Map Chinese region codes to legacy country codes. 698 */ 699 700 if (!strcmp(language, "zh") && !strcmp(country, "HANS")) 701 strlcpy(country, "CN", sizeof(country)); 702 if (!strcmp(language, "zh") && !strcmp(country, "HANT")) 703 strlcpy(country, "TW", sizeof(country)); 704 } 705 706 if (*language == '.' && !charset[0]) 707 { 708 /* 709 * Copy the encoding... 710 */ 711 712 for (language ++, ptr = charset; *language; language ++) 713 if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1)) 714 *ptr++ = (char)toupper(*language & 255); 715 716 *ptr = '\0'; 717 } 718 719 /* 720 * Force a POSIX locale for an invalid language name... 721 */ 722 723 if (strlen(langname) != 2 && strlen(langname) != 3) 724 { 725 strlcpy(langname, "C", sizeof(langname)); 726 country[0] = '\0'; 727 charset[0] = '\0'; 728 } 729 } 730 731 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"", 732 langname, country, charset)); 733 734 /* 735 * Figure out the desired encoding... 736 */ 737 738 encoding = CUPS_AUTO_ENCODING; 739 740 if (charset[0]) 741 { 742 for (i = 0; 743 i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0])); 744 i ++) 745 if (!_cups_strcasecmp(charset, locale_encodings[i])) 746 { 747 encoding = (cups_encoding_t)i; 748 break; 749 } 750 751 if (encoding == CUPS_AUTO_ENCODING) 752 { 753 /* 754 * Map alternate names for various character sets... 755 */ 756 757 if (!_cups_strcasecmp(charset, "iso-2022-jp") || 758 !_cups_strcasecmp(charset, "sjis")) 759 encoding = CUPS_WINDOWS_932; 760 else if (!_cups_strcasecmp(charset, "iso-2022-cn")) 761 encoding = CUPS_WINDOWS_936; 762 else if (!_cups_strcasecmp(charset, "iso-2022-kr")) 763 encoding = CUPS_WINDOWS_949; 764 else if (!_cups_strcasecmp(charset, "big5")) 765 encoding = CUPS_WINDOWS_950; 766 } 767 } 768 769 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding, 770 encoding == CUPS_AUTO_ENCODING ? "auto" : 771 lang_encodings[encoding])); 772 773 /* 774 * See if we already have this language/country loaded... 775 */ 776 777 if (country[0]) 778 snprintf(real, sizeof(real), "%s_%s", langname, country); 779 else 780 strlcpy(real, langname, sizeof(real)); 781 782 _cupsMutexLock(&lang_mutex); 783 784 if ((lang = cups_cache_lookup(real, encoding)) != NULL) 785 { 786 _cupsMutexUnlock(&lang_mutex); 787 788 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real)); 789 790 return (lang); 791 } 792 793 /* 794 * See if there is a free language available; if so, use that 795 * record... 796 */ 797 798 for (lang = lang_cache; lang != NULL; lang = lang->next) 799 if (lang->used == 0) 800 break; 801 802 if (lang == NULL) 803 { 804 /* 805 * Allocate memory for the language and add it to the cache. 806 */ 807 808 if ((lang = calloc(sizeof(cups_lang_t), 1)) == NULL) 809 { 810 _cupsMutexUnlock(&lang_mutex); 811 812 return (NULL); 813 } 814 815 lang->next = lang_cache; 816 lang_cache = lang; 817 } 818 else 819 { 820 /* 821 * Free all old strings as needed... 822 */ 823 824 _cupsMessageFree(lang->strings); 825 lang->strings = NULL; 826 } 827 828 /* 829 * Then assign the language and encoding fields... 830 */ 831 832 lang->used ++; 833 strlcpy(lang->language, real, sizeof(lang->language)); 834 835 if (encoding != CUPS_AUTO_ENCODING) 836 lang->encoding = encoding; 837 else 838 lang->encoding = CUPS_UTF8; 839 840 /* 841 * Return... 842 */ 843 844 _cupsMutexUnlock(&lang_mutex); 845 846 return (lang); 847 } 848 849 850 /* 851 * '_cupsLangString()' - Get a message string. 852 * 853 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to 854 * convert the string to the language encoding. 855 */ 856 857 const char * /* O - Localized message */ 858 _cupsLangString(cups_lang_t *lang, /* I - Language */ 859 const char *message) /* I - Message */ 860 { 861 const char *s; /* Localized message */ 862 863 864 DEBUG_printf(("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang, message)); 865 866 /* 867 * Range check input... 868 */ 869 870 if (!lang || !message || !*message) 871 return (message); 872 873 _cupsMutexLock(&lang_mutex); 874 875 /* 876 * Load the message catalog if needed... 877 */ 878 879 if (!lang->strings) 880 cups_message_load(lang); 881 882 s = _cupsMessageLookup(lang->strings, message); 883 884 _cupsMutexUnlock(&lang_mutex); 885 886 return (s); 887 } 888 889 890 /* 891 * '_cupsMessageFree()' - Free a messages array. 892 */ 893 894 void 895 _cupsMessageFree(cups_array_t *a) /* I - Message array */ 896 { 897 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR) 898 /* 899 * Release the cups.strings dictionary as needed... 900 */ 901 902 if (cupsArrayUserData(a)) 903 CFRelease((CFDictionaryRef)cupsArrayUserData(a)); 904 #endif /* __APPLE__ && CUPS_BUNDLEDIR */ 905 906 /* 907 * Free the array... 908 */ 909 910 cupsArrayDelete(a); 911 } 912 913 914 /* 915 * '_cupsMessageLoad()' - Load a .po file into a messages array. 916 */ 917 918 cups_array_t * /* O - New message array */ 919 _cupsMessageLoad(const char *filename, /* I - Message catalog to load */ 920 int unquote) /* I - Unescape \foo in strings? */ 921 { 922 cups_file_t *fp; /* Message file */ 923 cups_array_t *a; /* Message array */ 924 _cups_message_t *m; /* Current message */ 925 char s[4096], /* String buffer */ 926 *ptr, /* Pointer into buffer */ 927 *temp; /* New string */ 928 size_t length, /* Length of combined strings */ 929 ptrlen; /* Length of string */ 930 931 932 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename)); 933 934 /* 935 * Create an array to hold the messages... 936 */ 937 938 if ((a = _cupsMessageNew(NULL)) == NULL) 939 { 940 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!"); 941 return (NULL); 942 } 943 944 /* 945 * Open the message catalog file... 946 */ 947 948 if ((fp = cupsFileOpen(filename, "r")) == NULL) 949 { 950 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s", 951 strerror(errno))); 952 return (a); 953 } 954 955 /* 956 * Read messages from the catalog file until EOF... 957 * 958 * The format is the GNU gettext .po format, which is fairly simple: 959 * 960 * msgid "some text" 961 * msgstr "localized text" 962 * 963 * The ID and localized text can span multiple lines using the form: 964 * 965 * msgid "" 966 * "some long text" 967 * msgstr "" 968 * "localized text spanning " 969 * "multiple lines" 970 */ 971 972 m = NULL; 973 974 while (cupsFileGets(fp, s, sizeof(s)) != NULL) 975 { 976 /* 977 * Skip blank and comment lines... 978 */ 979 980 if (s[0] == '#' || !s[0]) 981 continue; 982 983 /* 984 * Strip the trailing quote... 985 */ 986 987 if ((ptr = strrchr(s, '\"')) == NULL) 988 continue; 989 990 *ptr = '\0'; 991 992 /* 993 * Find start of value... 994 */ 995 996 if ((ptr = strchr(s, '\"')) == NULL) 997 continue; 998 999 ptr ++; 1000 1001 /* 1002 * Unquote the text... 1003 */ 1004 1005 if (unquote) 1006 cups_unquote(ptr, ptr); 1007 1008 /* 1009 * Create or add to a message... 1010 */ 1011 1012 if (!strncmp(s, "msgid", 5)) 1013 { 1014 /* 1015 * Add previous message as needed... 1016 */ 1017 1018 if (m) 1019 { 1020 if (m->str && m->str[0]) 1021 { 1022 cupsArrayAdd(a, m); 1023 } 1024 else 1025 { 1026 /* 1027 * Translation is empty, don't add it... (STR #4033) 1028 */ 1029 1030 free(m->id); 1031 if (m->str) 1032 free(m->str); 1033 free(m); 1034 } 1035 } 1036 1037 /* 1038 * Create a new message with the given msgid string... 1039 */ 1040 1041 if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL) 1042 { 1043 cupsFileClose(fp); 1044 return (a); 1045 } 1046 1047 if ((m->id = strdup(ptr)) == NULL) 1048 { 1049 free(m); 1050 cupsFileClose(fp); 1051 return (a); 1052 } 1053 } 1054 else if (s[0] == '\"' && m) 1055 { 1056 /* 1057 * Append to current string... 1058 */ 1059 1060 length = strlen(m->str ? m->str : m->id); 1061 ptrlen = strlen(ptr); 1062 1063 if ((temp = realloc(m->str ? m->str : m->id, length + ptrlen + 1)) == NULL) 1064 { 1065 if (m->str) 1066 free(m->str); 1067 free(m->id); 1068 free(m); 1069 1070 cupsFileClose(fp); 1071 return (a); 1072 } 1073 1074 if (m->str) 1075 { 1076 /* 1077 * Copy the new portion to the end of the msgstr string - safe 1078 * to use memcpy because the buffer is allocated to the correct 1079 * size... 1080 */ 1081 1082 m->str = temp; 1083 1084 memcpy(m->str + length, ptr, ptrlen + 1); 1085 } 1086 else 1087 { 1088 /* 1089 * Copy the new portion to the end of the msgid string - safe 1090 * to use memcpy because the buffer is allocated to the correct 1091 * size... 1092 */ 1093 1094 m->id = temp; 1095 1096 memcpy(m->id + length, ptr, ptrlen + 1); 1097 } 1098 } 1099 else if (!strncmp(s, "msgstr", 6) && m) 1100 { 1101 /* 1102 * Set the string... 1103 */ 1104 1105 if ((m->str = strdup(ptr)) == NULL) 1106 { 1107 free(m->id); 1108 free(m); 1109 1110 cupsFileClose(fp); 1111 return (a); 1112 } 1113 } 1114 } 1115 1116 /* 1117 * Add the last message string to the array as needed... 1118 */ 1119 1120 if (m) 1121 { 1122 if (m->str && m->str[0]) 1123 { 1124 cupsArrayAdd(a, m); 1125 } 1126 else 1127 { 1128 /* 1129 * Translation is empty, don't add it... (STR #4033) 1130 */ 1131 1132 free(m->id); 1133 if (m->str) 1134 free(m->str); 1135 free(m); 1136 } 1137 } 1138 1139 /* 1140 * Close the message catalog file and return the new array... 1141 */ 1142 1143 cupsFileClose(fp); 1144 1145 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...", 1146 cupsArrayCount(a))); 1147 1148 return (a); 1149 } 1150 1151 1152 /* 1153 * '_cupsMessageLookup()' - Lookup a message string. 1154 */ 1155 1156 const char * /* O - Localized message */ 1157 _cupsMessageLookup(cups_array_t *a, /* I - Message array */ 1158 const char *m) /* I - Message */ 1159 { 1160 _cups_message_t key, /* Search key */ 1161 *match; /* Matching message */ 1162 1163 1164 DEBUG_printf(("_cupsMessageLookup(a=%p, m=\"%s\")", (void *)a, m)); 1165 1166 /* 1167 * Lookup the message string; if it doesn't exist in the catalog, 1168 * then return the message that was passed to us... 1169 */ 1170 1171 key.id = (char *)m; 1172 match = (_cups_message_t *)cupsArrayFind(a, &key); 1173 1174 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR) 1175 if (!match && cupsArrayUserData(a)) 1176 { 1177 /* 1178 * Try looking the string up in the cups.strings dictionary... 1179 */ 1180 1181 CFDictionaryRef dict; /* cups.strings dictionary */ 1182 CFStringRef cfm, /* Message as a CF string */ 1183 cfstr; /* Localized text as a CF string */ 1184 1185 dict = (CFDictionaryRef)cupsArrayUserData(a); 1186 cfm = CFStringCreateWithCString(kCFAllocatorDefault, m, 1187 kCFStringEncodingUTF8); 1188 match = calloc(1, sizeof(_cups_message_t)); 1189 match->id = strdup(m); 1190 cfstr = cfm ? CFDictionaryGetValue(dict, cfm) : NULL; 1191 1192 if (cfstr) 1193 { 1194 char buffer[1024]; /* Message buffer */ 1195 1196 CFStringGetCString(cfstr, buffer, sizeof(buffer), kCFStringEncodingUTF8); 1197 match->str = strdup(buffer); 1198 1199 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...", 1200 m, buffer)); 1201 } 1202 else 1203 { 1204 match->str = strdup(m); 1205 1206 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m)); 1207 } 1208 1209 cupsArrayAdd(a, match); 1210 1211 if (cfm) 1212 CFRelease(cfm); 1213 } 1214 #endif /* __APPLE__ && CUPS_BUNDLEDIR */ 1215 1216 if (match && match->str) 1217 return (match->str); 1218 else 1219 return (m); 1220 } 1221 1222 1223 /* 1224 * '_cupsMessageNew()' - Make a new message catalog array. 1225 */ 1226 1227 cups_array_t * /* O - Array */ 1228 _cupsMessageNew(void *context) /* I - User data */ 1229 { 1230 return (cupsArrayNew3((cups_array_func_t)cups_message_compare, context, 1231 (cups_ahash_func_t)NULL, 0, 1232 (cups_acopy_func_t)NULL, 1233 (cups_afree_func_t)cups_message_free)); 1234 } 1235 1236 1237 #ifdef __APPLE__ 1238 /* 1239 * 'appleLangDefault()' - Get the default locale string. 1240 */ 1241 1242 static const char * /* O - Locale string */ 1243 appleLangDefault(void) 1244 { 1245 CFBundleRef bundle; /* Main bundle (if any) */ 1246 CFArrayRef bundleList; /* List of localizations in bundle */ 1247 CFPropertyListRef localizationList = NULL; 1248 /* List of localization data */ 1249 CFStringRef languageName; /* Current name */ 1250 char *lang; /* LANG environment variable */ 1251 _cups_globals_t *cg = _cupsGlobals(); 1252 /* Pointer to library globals */ 1253 1254 1255 DEBUG_puts("2appleLangDefault()"); 1256 1257 /* 1258 * Only do the lookup and translation the first time. 1259 */ 1260 1261 if (!cg->language[0]) 1262 { 1263 if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL) 1264 { 1265 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang)); 1266 strlcpy(cg->language, lang, sizeof(cg->language)); 1267 return (cg->language); 1268 } 1269 else if ((bundle = CFBundleGetMainBundle()) != NULL && 1270 (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL) 1271 { 1272 CFURLRef resources = CFBundleCopyResourcesDirectoryURL(bundle); 1273 1274 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle."); 1275 1276 if (resources) 1277 { 1278 CFStringRef cfpath = CFURLCopyPath(resources); 1279 char path[1024]; 1280 1281 if (cfpath) 1282 { 1283 /* 1284 * See if we have an Info.plist file in the bundle... 1285 */ 1286 1287 CFStringGetCString(cfpath, path, sizeof(path), kCFStringEncodingUTF8); 1288 DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path)); 1289 strlcat(path, "Contents/Info.plist", sizeof(path)); 1290 1291 if (!access(path, R_OK)) 1292 localizationList = CFBundleCopyPreferredLocalizationsFromArray(bundleList); 1293 else 1294 DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL..."); 1295 1296 CFRelease(cfpath); 1297 } 1298 1299 CFRelease(resources); 1300 } 1301 else 1302 DEBUG_puts("3appleLangDefault: No resource URL."); 1303 1304 CFRelease(bundleList); 1305 } 1306 1307 if (!localizationList) 1308 { 1309 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences."); 1310 1311 localizationList = 1312 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"), 1313 kCFPreferencesCurrentApplication); 1314 } 1315 1316 if (localizationList) 1317 { 1318 #ifdef DEBUG 1319 if (CFGetTypeID(localizationList) == CFArrayGetTypeID()) 1320 DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.", 1321 (int)CFArrayGetCount(localizationList))); 1322 else 1323 DEBUG_puts("3appleLangDefault: Got localizationList but not an array."); 1324 #endif /* DEBUG */ 1325 1326 if (CFGetTypeID(localizationList) == CFArrayGetTypeID() && 1327 CFArrayGetCount(localizationList) > 0) 1328 { 1329 languageName = CFArrayGetValueAtIndex(localizationList, 0); 1330 1331 if (languageName && 1332 CFGetTypeID(languageName) == CFStringGetTypeID()) 1333 { 1334 if (_cupsAppleLocale(languageName, cg->language, sizeof(cg->language))) 1335 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"", 1336 cg->language)); 1337 else 1338 DEBUG_puts("3appleLangDefault: Unable to get locale."); 1339 } 1340 } 1341 1342 CFRelease(localizationList); 1343 } 1344 1345 /* 1346 * If we didn't find the language, default to en_US... 1347 */ 1348 1349 if (!cg->language[0]) 1350 { 1351 DEBUG_puts("3appleLangDefault: Defaulting to en_US."); 1352 strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language)); 1353 } 1354 } 1355 else 1356 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg->language)); 1357 1358 /* 1359 * Return the cached locale... 1360 */ 1361 1362 return (cg->language); 1363 } 1364 1365 1366 # ifdef CUPS_BUNDLEDIR 1367 /* 1368 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle. 1369 */ 1370 1371 static cups_array_t * /* O - Message catalog */ 1372 appleMessageLoad(const char *locale) /* I - Locale ID */ 1373 { 1374 char filename[1024], /* Path to cups.strings file */ 1375 applelang[256], /* Apple language ID */ 1376 baselang[4]; /* Base language */ 1377 CFURLRef url; /* URL to cups.strings file */ 1378 CFReadStreamRef stream = NULL; /* File stream */ 1379 CFPropertyListRef plist = NULL; /* Localization file */ 1380 #ifdef DEBUG 1381 const char *cups_strings = getenv("CUPS_STRINGS"); 1382 /* Test strings file */ 1383 CFErrorRef error = NULL; /* Error when opening file */ 1384 #endif /* DEBUG */ 1385 1386 1387 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale)); 1388 1389 /* 1390 * Load the cups.strings file... 1391 */ 1392 1393 #ifdef DEBUG 1394 if (cups_strings) 1395 { 1396 DEBUG_puts("1appleMessageLoad: Using debug CUPS_STRINGS file."); 1397 strlcpy(filename, cups_strings, sizeof(filename)); 1398 } 1399 else 1400 #endif /* DEBUG */ 1401 1402 snprintf(filename, sizeof(filename), 1403 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", 1404 _cupsAppleLanguage(locale, applelang, sizeof(applelang))); 1405 1406 if (access(filename, 0)) 1407 { 1408 /* 1409 * <rdar://problem/22086642> 1410 * 1411 * Try with original locale string... 1412 */ 1413 1414 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno))); 1415 snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale); 1416 } 1417 1418 if (access(filename, 0)) 1419 { 1420 /* 1421 * <rdar://problem/25292403> 1422 * 1423 * Try with just the language code... 1424 */ 1425 1426 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno))); 1427 1428 strlcpy(baselang, locale, sizeof(baselang)); 1429 if (baselang[3] == '-' || baselang[3] == '_') 1430 baselang[3] = '\0'; 1431 1432 snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", baselang); 1433 } 1434 1435 if (access(filename, 0)) 1436 { 1437 /* 1438 * Try alternate lproj directory names... 1439 */ 1440 1441 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno))); 1442 1443 if (!strncmp(locale, "en", 2)) 1444 locale = "English"; 1445 else if (!strncmp(locale, "nb", 2)) 1446 locale = "no"; 1447 else if (!strncmp(locale, "nl", 2)) 1448 locale = "Dutch"; 1449 else if (!strncmp(locale, "fr", 2)) 1450 locale = "French"; 1451 else if (!strncmp(locale, "de", 2)) 1452 locale = "German"; 1453 else if (!strncmp(locale, "it", 2)) 1454 locale = "Italian"; 1455 else if (!strncmp(locale, "ja", 2)) 1456 locale = "Japanese"; 1457 else if (!strncmp(locale, "es", 2)) 1458 locale = "Spanish"; 1459 else if (!strcmp(locale, "zh_HK") || !strncasecmp(locale, "zh-Hant", 7) || !strncasecmp(locale, "zh_Hant", 7)) 1460 { 1461 /* 1462 * <rdar://problem/22130168> 1463 * <rdar://problem/27245567> 1464 * 1465 * Try zh_TW first, then zh... Sigh... 1466 */ 1467 1468 if (!access(CUPS_BUNDLEDIR "/Resources/zh_TW.lproj/cups.strings", 0)) 1469 locale = "zh_TW"; 1470 else 1471 locale = "zh"; 1472 } 1473 else if (strstr(locale, "_") != NULL || strstr(locale, "-") != NULL) 1474 { 1475 /* 1476 * Drop country code, just try language... 1477 */ 1478 1479 strlcpy(baselang, locale, sizeof(baselang)); 1480 if (baselang[2] == '-' || baselang[2] == '_') 1481 baselang[2] = '\0'; 1482 1483 locale = baselang; 1484 } 1485 1486 snprintf(filename, sizeof(filename), 1487 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale); 1488 } 1489 1490 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename)); 1491 1492 url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, 1493 (UInt8 *)filename, 1494 (CFIndex)strlen(filename), false); 1495 if (url) 1496 { 1497 stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url); 1498 if (stream) 1499 { 1500 /* 1501 * Read the property list containing the localization data. 1502 * 1503 * NOTE: This code currently generates a clang "potential leak" 1504 * warning, but the object is released in _cupsMessageFree(). 1505 */ 1506 1507 CFReadStreamOpen(stream); 1508 1509 #ifdef DEBUG 1510 plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0, 1511 kCFPropertyListImmutable, NULL, 1512 &error); 1513 if (error) 1514 { 1515 CFStringRef msg = CFErrorCopyDescription(error); 1516 /* Error message */ 1517 1518 CFStringGetCString(msg, filename, sizeof(filename), 1519 kCFStringEncodingUTF8); 1520 DEBUG_printf(("1appleMessageLoad: %s", filename)); 1521 1522 CFRelease(msg); 1523 CFRelease(error); 1524 } 1525 1526 #else 1527 plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0, 1528 kCFPropertyListImmutable, NULL, 1529 NULL); 1530 #endif /* DEBUG */ 1531 1532 if (plist && CFGetTypeID(plist) != CFDictionaryGetTypeID()) 1533 { 1534 CFRelease(plist); 1535 plist = NULL; 1536 } 1537 1538 CFRelease(stream); 1539 } 1540 1541 CFRelease(url); 1542 } 1543 1544 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url, stream, 1545 plist)); 1546 1547 /* 1548 * Create and return an empty array to act as a cache for messages, passing the 1549 * plist as the user data. 1550 */ 1551 1552 return (_cupsMessageNew((void *)plist)); 1553 } 1554 # endif /* CUPS_BUNDLEDIR */ 1555 #endif /* __APPLE__ */ 1556 1557 1558 /* 1559 * 'cups_cache_lookup()' - Lookup a language in the cache... 1560 */ 1561 1562 static cups_lang_t * /* O - Language data or NULL */ 1563 cups_cache_lookup( 1564 const char *name, /* I - Name of locale */ 1565 cups_encoding_t encoding) /* I - Encoding of locale */ 1566 { 1567 cups_lang_t *lang; /* Current language */ 1568 1569 1570 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name, 1571 encoding, encoding == CUPS_AUTO_ENCODING ? "auto" : 1572 lang_encodings[encoding])); 1573 1574 /* 1575 * Loop through the cache and return a match if found... 1576 */ 1577 1578 for (lang = lang_cache; lang != NULL; lang = lang->next) 1579 { 1580 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", " 1581 "encoding=%d(%s)", (void *)lang, lang->language, lang->encoding, 1582 lang_encodings[lang->encoding])); 1583 1584 if (!strcmp(lang->language, name) && 1585 (encoding == CUPS_AUTO_ENCODING || encoding == lang->encoding)) 1586 { 1587 lang->used ++; 1588 1589 DEBUG_puts("8cups_cache_lookup: returning match!"); 1590 1591 return (lang); 1592 } 1593 } 1594 1595 DEBUG_puts("8cups_cache_lookup: returning NULL!"); 1596 1597 return (NULL); 1598 } 1599 1600 1601 /* 1602 * 'cups_message_compare()' - Compare two messages. 1603 */ 1604 1605 static int /* O - Result of comparison */ 1606 cups_message_compare( 1607 _cups_message_t *m1, /* I - First message */ 1608 _cups_message_t *m2) /* I - Second message */ 1609 { 1610 return (strcmp(m1->id, m2->id)); 1611 } 1612 1613 1614 /* 1615 * 'cups_message_free()' - Free a message. 1616 */ 1617 1618 static void 1619 cups_message_free(_cups_message_t *m) /* I - Message */ 1620 { 1621 if (m->id) 1622 free(m->id); 1623 1624 if (m->str) 1625 free(m->str); 1626 1627 free(m); 1628 } 1629 1630 1631 /* 1632 * 'cups_message_load()' - Load the message catalog for a language. 1633 */ 1634 1635 static void 1636 cups_message_load(cups_lang_t *lang) /* I - Language */ 1637 { 1638 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR) 1639 lang->strings = appleMessageLoad(lang->language); 1640 1641 #else 1642 char filename[1024]; /* Filename for language locale file */ 1643 _cups_globals_t *cg = _cupsGlobals(); 1644 /* Pointer to library globals */ 1645 1646 1647 snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir, 1648 lang->language, lang->language); 1649 1650 if (strchr(lang->language, '_') && access(filename, 0)) 1651 { 1652 /* 1653 * Country localization not available, look for generic localization... 1654 */ 1655 1656 snprintf(filename, sizeof(filename), "%s/%.2s/cups_%.2s.po", cg->localedir, 1657 lang->language, lang->language); 1658 1659 if (access(filename, 0)) 1660 { 1661 /* 1662 * No generic localization, so use POSIX... 1663 */ 1664 1665 DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename, 1666 strerror(errno))); 1667 1668 snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir); 1669 } 1670 } 1671 1672 /* 1673 * Read the strings from the file... 1674 */ 1675 1676 lang->strings = _cupsMessageLoad(filename, 1); 1677 #endif /* __APPLE__ && CUPS_BUNDLEDIR */ 1678 } 1679 1680 1681 /* 1682 * 'cups_unquote()' - Unquote characters in strings... 1683 */ 1684 1685 static void 1686 cups_unquote(char *d, /* O - Unquoted string */ 1687 const char *s) /* I - Original string */ 1688 { 1689 while (*s) 1690 { 1691 if (*s == '\\') 1692 { 1693 s ++; 1694 if (isdigit(*s)) 1695 { 1696 *d = 0; 1697 1698 while (isdigit(*s)) 1699 { 1700 *d = *d * 8 + *s - '0'; 1701 s ++; 1702 } 1703 1704 d ++; 1705 } 1706 else 1707 { 1708 if (*s == 'n') 1709 *d ++ = '\n'; 1710 else if (*s == 'r') 1711 *d ++ = '\r'; 1712 else if (*s == 't') 1713 *d ++ = '\t'; 1714 else 1715 *d++ = *s; 1716 1717 s ++; 1718 } 1719 } 1720 else 1721 *d++ = *s++; 1722 } 1723 1724 *d = '\0'; 1725 } 1726