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