diff options
Diffstat (limited to 'cups/language.c')
-rw-r--r-- | cups/language.c | 1635 |
1 files changed, 1635 insertions, 0 deletions
diff --git a/cups/language.c b/cups/language.c new file mode 100644 index 00000000..f1afeccd --- /dev/null +++ b/cups/language.c @@ -0,0 +1,1635 @@ +/* + * I18N/language support for CUPS. + * + * Copyright 2007-2016 by Apple Inc. + * Copyright 1997-2007 by Easy Software Products. + * + * These coded instructions, statements, and computer programs are the + * property of Apple Inc. and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "LICENSE.txt" + * which should have been included with this file. If this file is + * file is missing or damaged, see the license at "http://www.cups.org/". + * + * This file is subject to the Apple OS-Developed Software exception. + */ + +/* + * Include necessary headers... + */ + +#include "cups-private.h" +#ifdef HAVE_LANGINFO_H +# include <langinfo.h> +#endif /* HAVE_LANGINFO_H */ +#ifdef WIN32 +# include <io.h> +#else +# include <unistd.h> +#endif /* WIN32 */ +#ifdef HAVE_COREFOUNDATION_H +# include <CoreFoundation/CoreFoundation.h> +#endif /* HAVE_COREFOUNDATION_H */ + + +/* + * Local globals... + */ + +static _cups_mutex_t lang_mutex = _CUPS_MUTEX_INITIALIZER; + /* Mutex to control access to cache */ +static cups_lang_t *lang_cache = NULL; + /* Language string cache */ +static const char * const lang_encodings[] = + { /* Encoding strings */ + "us-ascii", "iso-8859-1", + "iso-8859-2", "iso-8859-3", + "iso-8859-4", "iso-8859-5", + "iso-8859-6", "iso-8859-7", + "iso-8859-8", "iso-8859-9", + "iso-8859-10", "utf-8", + "iso-8859-13", "iso-8859-14", + "iso-8859-15", "cp874", + "cp1250", "cp1251", + "cp1252", "cp1253", + "cp1254", "cp1255", + "cp1256", "cp1257", + "cp1258", "koi8-r", + "koi8-u", "iso-8859-11", + "iso-8859-16", "mac", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "cp932", "cp936", + "cp949", "cp950", + "cp1361", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "unknown", "unknown", + "euc-cn", "euc-jp", + "euc-kr", "euc-tw", + "shift_jisx0213" + }; + +#ifdef __APPLE__ +typedef struct +{ + const char * const language; /* Language ID */ + const char * const locale; /* Locale ID */ +} _apple_language_locale_t; + +static const _apple_language_locale_t apple_language_locale[] = +{ /* Locale to language ID LUT */ + { "en", "en_US" }, + { "nb", "no" }, + { "zh-Hans", "zh_CN" }, + { "zh-Hant", "zh_TW" } +}; +#endif /* __APPLE__ */ + + +/* + * Local functions... + */ + + +#ifdef __APPLE__ +static const char *appleLangDefault(void); +# ifdef CUPS_BUNDLEDIR +# ifndef CF_RETURNS_RETAINED +# if __has_feature(attribute_cf_returns_retained) +# define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) +# else +# define CF_RETURNS_RETAINED +# endif /* __has_feature(attribute_cf_returns_retained) */ +# endif /* !CF_RETURNED_RETAINED */ +static cups_array_t *appleMessageLoad(const char *locale) + CF_RETURNS_RETAINED; +# endif /* CUPS_BUNDLEDIR */ +#endif /* __APPLE__ */ +static cups_lang_t *cups_cache_lookup(const char *name, + cups_encoding_t encoding); +static int cups_message_compare(_cups_message_t *m1, + _cups_message_t *m2); +static void cups_message_free(_cups_message_t *m); +static void cups_message_load(cups_lang_t *lang); +static void cups_unquote(char *d, const char *s); + + +#ifdef __APPLE__ +/* + * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a + * locale ID. + */ + +const char * /* O - Language ID */ +_cupsAppleLanguage(const char *locale, /* I - Locale ID */ + char *language,/* I - Language ID buffer */ + size_t langsize) /* I - Size of language ID buffer */ +{ + int i; /* Looping var */ + CFStringRef localeid, /* CF locale identifier */ + langid; /* CF language identifier */ + + + /* + * Copy the locale name and convert, as needed, to the Apple-specific + * locale identifier... + */ + + switch (strlen(locale)) + { + default : + /* + * Invalid locale... + */ + + strlcpy(language, "en", langsize); + break; + + case 2 : + strlcpy(language, locale, langsize); + break; + + case 5 : + strlcpy(language, locale, langsize); + + if (language[2] == '-') + { + /* + * Convert ll-cc to ll_CC... + */ + + language[2] = '_'; + language[3] = (char)toupper(language[3] & 255); + language[4] = (char)toupper(language[4] & 255); + } + break; + } + + for (i = 0; + i < (int)(sizeof(apple_language_locale) / + sizeof(apple_language_locale[0])); + i ++) + if (!strcmp(locale, apple_language_locale[i].locale)) + { + strlcpy(language, apple_language_locale[i].language, sizeof(language)); + break; + } + + /* + * Attempt to map the locale ID to a language ID... + */ + + if ((localeid = CFStringCreateWithCString(kCFAllocatorDefault, language, + kCFStringEncodingASCII)) != NULL) + { + if ((langid = CFLocaleCreateCanonicalLanguageIdentifierFromString( + kCFAllocatorDefault, localeid)) != NULL) + { + CFStringGetCString(langid, language, (CFIndex)langsize, kCFStringEncodingASCII); + CFRelease(langid); + } + + CFRelease(localeid); + } + + /* + * Return what we got... + */ + + return (language); +} +#endif /* __APPLE__ */ + + +/* + * '_cupsEncodingName()' - Return the character encoding name string + * for the given encoding enumeration. + */ + +const char * /* O - Character encoding */ +_cupsEncodingName( + cups_encoding_t encoding) /* I - Encoding value */ +{ + if (encoding < CUPS_US_ASCII || + encoding >= (cups_encoding_t)(sizeof(lang_encodings) / sizeof(lang_encodings[0]))) + { + DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")", + encoding, lang_encodings[0])); + return (lang_encodings[0]); + } + else + { + DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"", + encoding, lang_encodings[encoding])); + return (lang_encodings[encoding]); + } +} + + +/* + * 'cupsLangDefault()' - Return the default language. + */ + +cups_lang_t * /* O - Language data */ +cupsLangDefault(void) +{ + return (cupsLangGet(NULL)); +} + + +/* + * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.) + * for the given language. + */ + +const char * /* O - Character encoding */ +cupsLangEncoding(cups_lang_t *lang) /* I - Language data */ +{ + if (lang == NULL) + return ((char*)lang_encodings[0]); + else + return ((char*)lang_encodings[lang->encoding]); +} + + +/* + * 'cupsLangFlush()' - Flush all language data out of the cache. + */ + +void +cupsLangFlush(void) +{ + cups_lang_t *lang, /* Current language */ + *next; /* Next language */ + + + /* + * Free all languages in the cache... + */ + + _cupsMutexLock(&lang_mutex); + + for (lang = lang_cache; lang != NULL; lang = next) + { + /* + * Free all messages... + */ + + _cupsMessageFree(lang->strings); + + /* + * Then free the language structure itself... + */ + + next = lang->next; + free(lang); + } + + lang_cache = NULL; + + _cupsMutexUnlock(&lang_mutex); +} + + +/* + * 'cupsLangFree()' - Free language data. + * + * This does not actually free anything; use @link cupsLangFlush@ for that. + */ + +void +cupsLangFree(cups_lang_t *lang) /* I - Language to free */ +{ + _cupsMutexLock(&lang_mutex); + + if (lang != NULL && lang->used > 0) + lang->used --; + + _cupsMutexUnlock(&lang_mutex); +} + + +/* + * 'cupsLangGet()' - Get a language. + */ + +cups_lang_t * /* O - Language data */ +cupsLangGet(const char *language) /* I - Language or locale */ +{ + int i; /* Looping var */ +#ifndef __APPLE__ + char locale[255]; /* Copy of locale name */ +#endif /* !__APPLE__ */ + char langname[16], /* Requested language name */ + country[16], /* Country code */ + charset[16], /* Character set */ + *csptr, /* Pointer to CODESET string */ + *ptr, /* Pointer into language/charset */ + real[48]; /* Real language name */ + cups_encoding_t encoding; /* Encoding to use */ + cups_lang_t *lang; /* Current language... */ + static const char * const locale_encodings[] = + { /* Locale charset names */ + "ASCII", "ISO88591", "ISO88592", "ISO88593", + "ISO88594", "ISO88595", "ISO88596", "ISO88597", + "ISO88598", "ISO88599", "ISO885910", "UTF8", + "ISO885913", "ISO885914", "ISO885915", "CP874", + "CP1250", "CP1251", "CP1252", "CP1253", + "CP1254", "CP1255", "CP1256", "CP1257", + "CP1258", "KOI8R", "KOI8U", "ISO885911", + "ISO885916", "MACROMAN", "", "", + + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + + "CP932", "CP936", "CP949", "CP950", + "CP1361", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + + "EUCCN", "EUCJP", "EUCKR", "EUCTW", + "SHIFT_JISX0213" + }; + + + DEBUG_printf(("2cupsLangGet(language=\"%s\")", language)); + +#ifdef __APPLE__ + /* + * Set the character set to UTF-8... + */ + + strlcpy(charset, "UTF8", sizeof(charset)); + + /* + * Apple's setlocale doesn't give us the user's localization + * preference so we have to look it up this way... + */ + + if (!language) + { + if (!getenv("SOFTWARE") || (language = getenv("LANG")) == NULL) + language = appleLangDefault(); + + DEBUG_printf(("4cupsLangGet: language=\"%s\"", language)); + } + +#else + /* + * Set the charset to "unknown"... + */ + + charset[0] = '\0'; + + /* + * Use setlocale() to determine the currently set locale, and then + * fallback to environment variables to avoid setting the locale, + * since setlocale() is not thread-safe! + */ + + if (!language) + { + /* + * First see if the locale has been set; if it is still "C" or + * "POSIX", use the environment to get the default... + */ + +# ifdef LC_MESSAGES + ptr = setlocale(LC_MESSAGES, NULL); +# else + ptr = setlocale(LC_ALL, NULL); +# endif /* LC_MESSAGES */ + + DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr)); + + if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX")) + { + /* + * Get the character set from the LC_CTYPE locale setting... + */ + + if ((ptr = getenv("LC_CTYPE")) == NULL) + if ((ptr = getenv("LC_ALL")) == NULL) + if ((ptr = getenv("LANG")) == NULL) + ptr = "en_US"; + + if ((csptr = strchr(ptr, '.')) != NULL) + { + /* + * Extract the character set from the environment... + */ + + for (ptr = charset, csptr ++; *csptr; csptr ++) + if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr)) + *ptr++ = *csptr; + + *ptr = '\0'; + } + + /* + * Get the locale for messages from the LC_MESSAGES locale setting... + */ + + if ((ptr = getenv("LC_MESSAGES")) == NULL) + if ((ptr = getenv("LC_ALL")) == NULL) + if ((ptr = getenv("LANG")) == NULL) + ptr = "en_US"; + } + + if (ptr) + { + strlcpy(locale, ptr, sizeof(locale)); + language = locale; + + /* + * CUPS STR #2575: Map "nb" to "no" for back-compatibility... + */ + + if (!strncmp(locale, "nb", 2)) + locale[1] = 'o'; + + DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language)); + } + } +#endif /* __APPLE__ */ + + /* + * If "language" is NULL at this point, then chances are we are using + * a language that is not installed for the base OS. + */ + + if (!language) + { + /* + * Switch to the POSIX ("C") locale... + */ + + language = "C"; + } + +#ifdef CODESET + /* + * On systems that support the nl_langinfo(CODESET) call, use + * this value as the character set... + */ + + if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL) + { + /* + * Copy all of the letters and numbers in the CODESET string... + */ + + for (ptr = charset; *csptr; csptr ++) + if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1)) + *ptr++ = *csptr; + + *ptr = '\0'; + + DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via " + "nl_langinfo(CODESET)...", charset)); + } +#endif /* CODESET */ + + /* + * If we don't have a character set by now, default to UTF-8... + */ + + if (!charset[0]) + strlcpy(charset, "UTF8", sizeof(charset)); + + /* + * Parse the language string passed in to a locale string. "C" is the + * standard POSIX locale and is copied unchanged. Otherwise the + * language string is converted from ll-cc[.charset] (language-country) + * to ll_CC[.CHARSET] to match the file naming convention used by all + * POSIX-compliant operating systems. Invalid language names are mapped + * to the POSIX locale. + */ + + country[0] = '\0'; + + if (language == NULL || !language[0] || + !strcmp(language, "POSIX")) + strlcpy(langname, "C", sizeof(langname)); + else + { + /* + * Copy the parts of the locale string over safely... + */ + + for (ptr = langname; *language; language ++) + if (*language == '_' || *language == '-' || *language == '.') + break; + else if (ptr < (langname + sizeof(langname) - 1)) + *ptr++ = (char)tolower(*language & 255); + + *ptr = '\0'; + + if (*language == '_' || *language == '-') + { + /* + * Copy the country code... + */ + + for (language ++, ptr = country; *language; language ++) + if (*language == '.') + break; + else if (ptr < (country + sizeof(country) - 1)) + *ptr++ = (char)toupper(*language & 255); + + *ptr = '\0'; + } + + if (*language == '.' && !charset[0]) + { + /* + * Copy the encoding... + */ + + for (language ++, ptr = charset; *language; language ++) + if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1)) + *ptr++ = (char)toupper(*language & 255); + + *ptr = '\0'; + } + + /* + * Force a POSIX locale for an invalid language name... + */ + + if (strlen(langname) != 2) + { + strlcpy(langname, "C", sizeof(langname)); + country[0] = '\0'; + charset[0] = '\0'; + } + } + + DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"", + langname, country, charset)); + + /* + * Figure out the desired encoding... + */ + + encoding = CUPS_AUTO_ENCODING; + + if (charset[0]) + { + for (i = 0; + i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0])); + i ++) + if (!_cups_strcasecmp(charset, locale_encodings[i])) + { + encoding = (cups_encoding_t)i; + break; + } + + if (encoding == CUPS_AUTO_ENCODING) + { + /* + * Map alternate names for various character sets... + */ + + if (!_cups_strcasecmp(charset, "iso-2022-jp") || + !_cups_strcasecmp(charset, "sjis")) + encoding = CUPS_WINDOWS_932; + else if (!_cups_strcasecmp(charset, "iso-2022-cn")) + encoding = CUPS_WINDOWS_936; + else if (!_cups_strcasecmp(charset, "iso-2022-kr")) + encoding = CUPS_WINDOWS_949; + else if (!_cups_strcasecmp(charset, "big5")) + encoding = CUPS_WINDOWS_950; + } + } + + DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding, + encoding == CUPS_AUTO_ENCODING ? "auto" : + lang_encodings[encoding])); + + /* + * See if we already have this language/country loaded... + */ + + if (country[0]) + snprintf(real, sizeof(real), "%s_%s", langname, country); + else + strlcpy(real, langname, sizeof(real)); + + _cupsMutexLock(&lang_mutex); + + if ((lang = cups_cache_lookup(real, encoding)) != NULL) + { + _cupsMutexUnlock(&lang_mutex); + + DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real)); + + return (lang); + } + + /* + * See if there is a free language available; if so, use that + * record... + */ + + for (lang = lang_cache; lang != NULL; lang = lang->next) + if (lang->used == 0) + break; + + if (lang == NULL) + { + /* + * Allocate memory for the language and add it to the cache. + */ + + if ((lang = calloc(sizeof(cups_lang_t), 1)) == NULL) + { + _cupsMutexUnlock(&lang_mutex); + + return (NULL); + } + + lang->next = lang_cache; + lang_cache = lang; + } + else + { + /* + * Free all old strings as needed... + */ + + _cupsMessageFree(lang->strings); + lang->strings = NULL; + } + + /* + * Then assign the language and encoding fields... + */ + + lang->used ++; + strlcpy(lang->language, real, sizeof(lang->language)); + + if (encoding != CUPS_AUTO_ENCODING) + lang->encoding = encoding; + else + lang->encoding = CUPS_UTF8; + + /* + * Return... + */ + + _cupsMutexUnlock(&lang_mutex); + + return (lang); +} + + +/* + * '_cupsLangString()' - Get a message string. + * + * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to + * convert the string to the language encoding. + */ + +const char * /* O - Localized message */ +_cupsLangString(cups_lang_t *lang, /* I - Language */ + const char *message) /* I - Message */ +{ + const char *s; /* Localized message */ + + /* + * Range check input... + */ + + if (!lang || !message || !*message) + return (message); + + _cupsMutexLock(&lang_mutex); + + /* + * Load the message catalog if needed... + */ + + if (!lang->strings) + cups_message_load(lang); + + s = _cupsMessageLookup(lang->strings, message); + + _cupsMutexUnlock(&lang_mutex); + + return (s); +} + + +/* + * '_cupsMessageFree()' - Free a messages array. + */ + +void +_cupsMessageFree(cups_array_t *a) /* I - Message array */ +{ +#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR) + /* + * Release the cups.strings dictionary as needed... + */ + + if (cupsArrayUserData(a)) + CFRelease((CFDictionaryRef)cupsArrayUserData(a)); +#endif /* __APPLE__ && CUPS_BUNDLEDIR */ + + /* + * Free the array... + */ + + cupsArrayDelete(a); +} + + +/* + * '_cupsMessageLoad()' - Load a .po file into a messages array. + */ + +cups_array_t * /* O - New message array */ +_cupsMessageLoad(const char *filename, /* I - Message catalog to load */ + int unquote) /* I - Unescape \foo in strings? */ +{ + cups_file_t *fp; /* Message file */ + cups_array_t *a; /* Message array */ + _cups_message_t *m; /* Current message */ + char s[4096], /* String buffer */ + *ptr, /* Pointer into buffer */ + *temp; /* New string */ + size_t length, /* Length of combined strings */ + ptrlen; /* Length of string */ + + + DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename)); + + /* + * Create an array to hold the messages... + */ + + if ((a = _cupsMessageNew(NULL)) == NULL) + { + DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!"); + return (NULL); + } + + /* + * Open the message catalog file... + */ + + if ((fp = cupsFileOpen(filename, "r")) == NULL) + { + DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s", + strerror(errno))); + return (a); + } + + /* + * Read messages from the catalog file until EOF... + * + * The format is the GNU gettext .po format, which is fairly simple: + * + * msgid "some text" + * msgstr "localized text" + * + * The ID and localized text can span multiple lines using the form: + * + * msgid "" + * "some long text" + * msgstr "" + * "localized text spanning " + * "multiple lines" + */ + + m = NULL; + + while (cupsFileGets(fp, s, sizeof(s)) != NULL) + { + /* + * Skip blank and comment lines... + */ + + if (s[0] == '#' || !s[0]) + continue; + + /* + * Strip the trailing quote... + */ + + if ((ptr = strrchr(s, '\"')) == NULL) + continue; + + *ptr = '\0'; + + /* + * Find start of value... + */ + + if ((ptr = strchr(s, '\"')) == NULL) + continue; + + ptr ++; + + /* + * Unquote the text... + */ + + if (unquote) + cups_unquote(ptr, ptr); + + /* + * Create or add to a message... + */ + + if (!strncmp(s, "msgid", 5)) + { + /* + * Add previous message as needed... + */ + + if (m) + { + if (m->str && m->str[0]) + { + cupsArrayAdd(a, m); + } + else + { + /* + * Translation is empty, don't add it... (STR #4033) + */ + + free(m->id); + if (m->str) + free(m->str); + free(m); + } + } + + /* + * Create a new message with the given msgid string... + */ + + if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL) + { + cupsFileClose(fp); + return (a); + } + + if ((m->id = strdup(ptr)) == NULL) + { + free(m); + cupsFileClose(fp); + return (a); + } + } + else if (s[0] == '\"' && m) + { + /* + * Append to current string... + */ + + length = strlen(m->str ? m->str : m->id); + ptrlen = strlen(ptr); + + if ((temp = realloc(m->str ? m->str : m->id, length + ptrlen + 1)) == NULL) + { + if (m->str) + free(m->str); + free(m->id); + free(m); + + cupsFileClose(fp); + return (a); + } + + if (m->str) + { + /* + * Copy the new portion to the end of the msgstr string - safe + * to use memcpy because the buffer is allocated to the correct + * size... + */ + + m->str = temp; + + memcpy(m->str + length, ptr, ptrlen + 1); + } + else + { + /* + * Copy the new portion to the end of the msgid string - safe + * to use memcpy because the buffer is allocated to the correct + * size... + */ + + m->id = temp; + + memcpy(m->id + length, ptr, ptrlen + 1); + } + } + else if (!strncmp(s, "msgstr", 6) && m) + { + /* + * Set the string... + */ + + if ((m->str = strdup(ptr)) == NULL) + { + free(m->id); + free(m); + + cupsFileClose(fp); + return (a); + } + } + } + + /* + * Add the last message string to the array as needed... + */ + + if (m) + { + if (m->str && m->str[0]) + { + cupsArrayAdd(a, m); + } + else + { + /* + * Translation is empty, don't add it... (STR #4033) + */ + + free(m->id); + if (m->str) + free(m->str); + free(m); + } + } + + /* + * Close the message catalog file and return the new array... + */ + + cupsFileClose(fp); + + DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...", + cupsArrayCount(a))); + + return (a); +} + + +/* + * '_cupsMessageLookup()' - Lookup a message string. + */ + +const char * /* O - Localized message */ +_cupsMessageLookup(cups_array_t *a, /* I - Message array */ + const char *m) /* I - Message */ +{ + _cups_message_t key, /* Search key */ + *match; /* Matching message */ + + + /* + * Lookup the message string; if it doesn't exist in the catalog, + * then return the message that was passed to us... + */ + + key.id = (char *)m; + match = (_cups_message_t *)cupsArrayFind(a, &key); + +#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR) + if (!match && cupsArrayUserData(a)) + { + /* + * Try looking the string up in the cups.strings dictionary... + */ + + CFDictionaryRef dict; /* cups.strings dictionary */ + CFStringRef cfm, /* Message as a CF string */ + cfstr; /* Localized text as a CF string */ + + dict = (CFDictionaryRef)cupsArrayUserData(a); + cfm = CFStringCreateWithCString(kCFAllocatorDefault, m, + kCFStringEncodingUTF8); + match = calloc(1, sizeof(_cups_message_t)); + match->id = strdup(m); + cfstr = cfm ? CFDictionaryGetValue(dict, cfm) : NULL; + + if (cfstr) + { + char buffer[1024]; /* Message buffer */ + + CFStringGetCString(cfstr, buffer, sizeof(buffer), kCFStringEncodingUTF8); + match->str = strdup(buffer); + + DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...", + m, buffer)); + } + else + { + match->str = strdup(m); + + DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m)); + } + + cupsArrayAdd(a, match); + + if (cfm) + CFRelease(cfm); + } +#endif /* __APPLE__ && CUPS_BUNDLEDIR */ + + if (match && match->str) + return (match->str); + else + return (m); +} + + +/* + * '_cupsMessageNew()' - Make a new message catalog array. + */ + +cups_array_t * /* O - Array */ +_cupsMessageNew(void *context) /* I - User data */ +{ + return (cupsArrayNew3((cups_array_func_t)cups_message_compare, context, + (cups_ahash_func_t)NULL, 0, + (cups_acopy_func_t)NULL, + (cups_afree_func_t)cups_message_free)); +} + + +#ifdef __APPLE__ +/* + * 'appleLangDefault()' - Get the default locale string. + */ + +static const char * /* O - Locale string */ +appleLangDefault(void) +{ + int i; /* Looping var */ + CFBundleRef bundle; /* Main bundle (if any) */ + CFArrayRef bundleList; /* List of localizations in bundle */ + CFPropertyListRef localizationList = NULL; + /* List of localization data */ + CFStringRef languageName; /* Current name */ + CFStringRef localeName; /* Canonical from of name */ + char *lang; /* LANG environment variable */ + _cups_globals_t *cg = _cupsGlobals(); + /* Pointer to library globals */ + + + DEBUG_puts("2appleLangDefault()"); + + /* + * Only do the lookup and translation the first time. + */ + + if (!cg->language[0]) + { + if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL) + { + DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang)); + strlcpy(cg->language, lang, sizeof(cg->language)); + return (cg->language); + } + else if ((bundle = CFBundleGetMainBundle()) != NULL && + (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL) + { + CFURLRef resources = CFBundleCopyResourcesDirectoryURL(bundle); + + DEBUG_puts("3appleLangDefault: Getting localizationList from bundle."); + + if (resources) + { + CFStringRef cfpath = CFURLCopyPath(resources); + char path[1024]; + + if (cfpath) + { + /* + * See if we have an Info.plist file in the bundle... + */ + + CFStringGetCString(cfpath, path, sizeof(path), kCFStringEncodingUTF8); + DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path)); + strlcat(path, "Contents/Info.plist", sizeof(path)); + + if (!access(path, R_OK)) + localizationList = CFBundleCopyPreferredLocalizationsFromArray(bundleList); + else + DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL..."); + + CFRelease(cfpath); + } + + CFRelease(resources); + } + else + DEBUG_puts("3appleLangDefault: No resource URL."); + + CFRelease(bundleList); + } + + if (!localizationList) + { + DEBUG_puts("3appleLangDefault: Getting localizationList from preferences."); + + localizationList = + CFPreferencesCopyAppValue(CFSTR("AppleLanguages"), + kCFPreferencesCurrentApplication); + } + + if (localizationList) + { +#ifdef DEBUG + if (CFGetTypeID(localizationList) == CFArrayGetTypeID()) + DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.", + (int)CFArrayGetCount(localizationList))); + else + DEBUG_puts("3appleLangDefault: Got localizationList but not an array."); +#endif /* DEBUG */ + + if (CFGetTypeID(localizationList) == CFArrayGetTypeID() && + CFArrayGetCount(localizationList) > 0) + { + languageName = CFArrayGetValueAtIndex(localizationList, 0); + + if (languageName && + CFGetTypeID(languageName) == CFStringGetTypeID()) + { + localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString( + kCFAllocatorDefault, languageName); + + if (localeName) + { + CFStringGetCString(localeName, cg->language, sizeof(cg->language), + kCFStringEncodingASCII); + CFRelease(localeName); + + DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"", + cg->language)); + + /* + * Map new language identifiers to locales... + */ + + for (i = 0; + i < (int)(sizeof(apple_language_locale) / + sizeof(apple_language_locale[0])); + i ++) + { + if (!strcmp(cg->language, apple_language_locale[i].language)) + { + DEBUG_printf(("3appleLangDefault: mapping \"%s\" to \"%s\"...", + cg->language, apple_language_locale[i].locale)); + strlcpy(cg->language, apple_language_locale[i].locale, + sizeof(cg->language)); + break; + } + } + + /* + * Convert language subtag into region subtag... + */ + + if (cg->language[2] == '-') + cg->language[2] = '_'; + + if (!strchr(cg->language, '.')) + strlcat(cg->language, ".UTF-8", sizeof(cg->language)); + } + else + DEBUG_puts("3appleLangDefault: Unable to get localeName."); + } + } + + CFRelease(localizationList); + } + + /* + * If we didn't find the language, default to en_US... + */ + + if (!cg->language[0]) + { + DEBUG_puts("3appleLangDefault: Defaulting to en_US."); + strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language)); + } + } + else + DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg->language)); + + /* + * Return the cached locale... + */ + + return (cg->language); +} + + +# ifdef CUPS_BUNDLEDIR +/* + * 'appleMessageLoad()' - Load a message catalog from a localizable bundle. + */ + +static cups_array_t * /* O - Message catalog */ +appleMessageLoad(const char *locale) /* I - Locale ID */ +{ + char filename[1024], /* Path to cups.strings file */ + applelang[256], /* Apple language ID */ + baselang[3]; /* Base language */ + CFURLRef url; /* URL to cups.strings file */ + CFReadStreamRef stream = NULL; /* File stream */ + CFPropertyListRef plist = NULL; /* Localization file */ +#ifdef DEBUG + CFErrorRef error = NULL; /* Error when opening file */ +#endif /* DEBUG */ + + + DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale)); + + /* + * Load the cups.strings file... + */ + + snprintf(filename, sizeof(filename), + CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", + _cupsAppleLanguage(locale, applelang, sizeof(applelang))); + + if (access(filename, 0)) + { + /* + * <rdar://problem/22086642> + * + * Try with original locale string... + */ + + snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale); + } + + if (access(filename, 0)) + { + /* + * <rdar://problem/25292403> + * + * Try with just the language code... + */ + + strlcpy(baselang, locale, sizeof(baselang)); + snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", baselang); + } + + DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename)); + + if (access(filename, 0)) + { + /* + * Try alternate lproj directory names... + */ + + if (!strncmp(locale, "en", 2)) + locale = "English"; + else if (!strncmp(locale, "nb", 2)) + locale = "no"; + else if (!strncmp(locale, "nl", 2)) + locale = "Dutch"; + else if (!strncmp(locale, "fr", 2)) + locale = "French"; + else if (!strncmp(locale, "de", 2)) + locale = "German"; + else if (!strncmp(locale, "it", 2)) + locale = "Italian"; + else if (!strncmp(locale, "ja", 2)) + locale = "Japanese"; + else if (!strncmp(locale, "es", 2)) + locale = "Spanish"; + else if (!strcmp(locale, "zh_HK")) + { + /* + * <rdar://problem/22130168> + * + * Try zh_TW first, then zh... Sigh... + */ + + if (!access(CUPS_BUNDLEDIR "/Resources/zh_TW.lproj/cups.strings", 0)) + locale = "zh_TW"; + else + locale = "zh"; + } + else if (strstr(locale, "_") != NULL || strstr(locale, "-") != NULL) + { + /* + * Drop country code, just try language... + */ + + strlcpy(baselang, locale, sizeof(baselang)); + locale = baselang; + } + + snprintf(filename, sizeof(filename), + CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale); + DEBUG_printf(("1appleMessageLoad: alternate filename=\"%s\"", filename)); + } + + url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, + (UInt8 *)filename, + (CFIndex)strlen(filename), false); + if (url) + { + stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url); + if (stream) + { + /* + * Read the property list containing the localization data. + * + * NOTE: This code currently generates a clang "potential leak" + * warning, but the object is released in _cupsMessageFree(). + */ + + CFReadStreamOpen(stream); + +#ifdef DEBUG + plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0, + kCFPropertyListImmutable, NULL, + &error); + if (error) + { + CFStringRef msg = CFErrorCopyDescription(error); + /* Error message */ + + CFStringGetCString(msg, filename, sizeof(filename), + kCFStringEncodingUTF8); + DEBUG_printf(("1appleMessageLoad: %s", filename)); + + CFRelease(msg); + CFRelease(error); + } + +#else + plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0, + kCFPropertyListImmutable, NULL, + NULL); +#endif /* DEBUG */ + + if (plist && CFGetTypeID(plist) != CFDictionaryGetTypeID()) + { + CFRelease(plist); + plist = NULL; + } + + CFRelease(stream); + } + + CFRelease(url); + } + + DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url, stream, + plist)); + + /* + * Create and return an empty array to act as a cache for messages, passing the + * plist as the user data. + */ + + return (_cupsMessageNew((void *)plist)); +} +# endif /* CUPS_BUNDLEDIR */ +#endif /* __APPLE__ */ + + +/* + * 'cups_cache_lookup()' - Lookup a language in the cache... + */ + +static cups_lang_t * /* O - Language data or NULL */ +cups_cache_lookup( + const char *name, /* I - Name of locale */ + cups_encoding_t encoding) /* I - Encoding of locale */ +{ + cups_lang_t *lang; /* Current language */ + + + DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name, + encoding, encoding == CUPS_AUTO_ENCODING ? "auto" : + lang_encodings[encoding])); + + /* + * Loop through the cache and return a match if found... + */ + + for (lang = lang_cache; lang != NULL; lang = lang->next) + { + DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", " + "encoding=%d(%s)", (void *)lang, lang->language, lang->encoding, + lang_encodings[lang->encoding])); + + if (!strcmp(lang->language, name) && + (encoding == CUPS_AUTO_ENCODING || encoding == lang->encoding)) + { + lang->used ++; + + DEBUG_puts("8cups_cache_lookup: returning match!"); + + return (lang); + } + } + + DEBUG_puts("8cups_cache_lookup: returning NULL!"); + + return (NULL); +} + + +/* + * 'cups_message_compare()' - Compare two messages. + */ + +static int /* O - Result of comparison */ +cups_message_compare( + _cups_message_t *m1, /* I - First message */ + _cups_message_t *m2) /* I - Second message */ +{ + return (strcmp(m1->id, m2->id)); +} + + +/* + * 'cups_message_free()' - Free a message. + */ + +static void +cups_message_free(_cups_message_t *m) /* I - Message */ +{ + if (m->id) + free(m->id); + + if (m->str) + free(m->str); + + free(m); +} + + +/* + * 'cups_message_load()' - Load the message catalog for a language. + */ + +static void +cups_message_load(cups_lang_t *lang) /* I - Language */ +{ +#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR) + lang->strings = appleMessageLoad(lang->language); + +#else + char filename[1024]; /* Filename for language locale file */ + _cups_globals_t *cg = _cupsGlobals(); + /* Pointer to library globals */ + + + snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir, + lang->language, lang->language); + + if (strchr(lang->language, '_') && access(filename, 0)) + { + /* + * Country localization not available, look for generic localization... + */ + + snprintf(filename, sizeof(filename), "%s/%.2s/cups_%.2s.po", cg->localedir, + lang->language, lang->language); + + if (access(filename, 0)) + { + /* + * No generic localization, so use POSIX... + */ + + DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename, + strerror(errno))); + + snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir); + } + } + + /* + * Read the strings from the file... + */ + + lang->strings = _cupsMessageLoad(filename, 1); +#endif /* __APPLE__ && CUPS_BUNDLEDIR */ +} + + +/* + * 'cups_unquote()' - Unquote characters in strings... + */ + +static void +cups_unquote(char *d, /* O - Unquoted string */ + const char *s) /* I - Original string */ +{ + while (*s) + { + if (*s == '\\') + { + s ++; + if (isdigit(*s)) + { + *d = 0; + + while (isdigit(*s)) + { + *d = *d * 8 + *s - '0'; + s ++; + } + + d ++; + } + else + { + if (*s == 'n') + *d ++ = '\n'; + else if (*s == 'r') + *d ++ = '\r'; + else if (*s == 't') + *d ++ = '\t'; + else + *d++ = *s; + + s ++; + } + } + else + *d++ = *s++; + } + + *d = '\0'; +} |