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