2 * I18N/language support for CUPS.
4 * Copyright 2007-2017 by Apple Inc.
5 * Copyright 1997-2007 by Easy Software Products.
7 * These coded instructions, statements, and computer programs are the
8 * property of Apple Inc. and are protected by Federal copyright
9 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
10 * which should have been included with this file. If this file is
11 * missing or damaged, see the license at "http://www.cups.org/".
13 * This file is subject to the Apple OS-Developed Software exception.
17 * Include necessary headers...
20 #include "cups-private.h"
21 #ifdef HAVE_LANGINFO_H
22 # include <langinfo.h>
23 #endif /* HAVE_LANGINFO_H */
29 #ifdef HAVE_COREFOUNDATION_H
30 # include <CoreFoundation/CoreFoundation.h>
31 #endif /* HAVE_COREFOUNDATION_H */
38 static _cups_mutex_t lang_mutex
= _CUPS_MUTEX_INITIALIZER
;
39 /* Mutex to control access to cache */
40 static cups_lang_t
*lang_cache
= NULL
;
41 /* Language string cache */
42 static const char * const lang_encodings
[] =
43 { /* Encoding strings */
44 "us-ascii", "iso-8859-1",
45 "iso-8859-2", "iso-8859-3",
46 "iso-8859-4", "iso-8859-5",
47 "iso-8859-6", "iso-8859-7",
48 "iso-8859-8", "iso-8859-9",
49 "iso-8859-10", "utf-8",
50 "iso-8859-13", "iso-8859-14",
51 "iso-8859-15", "cp874",
57 "koi8-u", "iso-8859-11",
100 "unknown", "unknown",
101 "unknown", "unknown",
102 "unknown", "unknown",
103 "unknown", "unknown",
104 "unknown", "unknown",
105 "unknown", "unknown",
106 "unknown", "unknown",
107 "unknown", "unknown",
116 const char * const language
; /* Language ID */
117 const char * const locale
; /* Locale ID */
118 } _apple_language_locale_t
;
120 static const _apple_language_locale_t apple_language_locale
[] =
121 { /* Language to locale ID LUT */
125 { "zh-Hans", "zh_CN" },
126 { "zh_HANS", "zh_CN" },
127 { "zh-Hant", "zh_TW" },
128 { "zh_HANT", "zh_TW" },
129 { "zh-Hant_CN", "zh_TW" }
131 #endif /* __APPLE__ */
140 static const char *appleLangDefault(void);
141 # ifdef CUPS_BUNDLEDIR
142 # ifndef CF_RETURNS_RETAINED
143 # if __has_feature(attribute_cf_returns_retained)
144 # define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
146 # define CF_RETURNS_RETAINED
147 # endif /* __has_feature(attribute_cf_returns_retained) */
148 # endif /* !CF_RETURNED_RETAINED */
149 static cups_array_t
*appleMessageLoad(const char *locale
)
151 # endif /* CUPS_BUNDLEDIR */
152 #endif /* __APPLE__ */
153 static cups_lang_t
*cups_cache_lookup(const char *name
,
154 cups_encoding_t encoding
);
155 static int cups_message_compare(_cups_message_t
*m1
,
156 _cups_message_t
*m2
);
157 static void cups_message_free(_cups_message_t
*m
);
158 static void cups_message_load(cups_lang_t
*lang
);
159 static void cups_unquote(char *d
, const char *s
);
164 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
168 const char * /* O - Language ID */
169 _cupsAppleLanguage(const char *locale
, /* I - Locale ID */
170 char *language
,/* I - Language ID buffer */
171 size_t langsize
) /* I - Size of language ID buffer */
173 int i
; /* Looping var */
174 CFStringRef localeid
, /* CF locale identifier */
175 langid
; /* CF language identifier */
179 * Copy the locale name and convert, as needed, to the Apple-specific
180 * locale identifier...
183 switch (strlen(locale
))
190 strlcpy(language
, "en", langsize
);
194 strlcpy(language
, locale
, langsize
);
198 strlcpy(language
, locale
, langsize
);
200 if (language
[2] == '-')
203 * Convert ll-cc to ll_CC...
207 language
[3] = (char)toupper(language
[3] & 255);
208 language
[4] = (char)toupper(language
[4] & 255);
214 i
< (int)(sizeof(apple_language_locale
) /
215 sizeof(apple_language_locale
[0]));
217 if (!strcmp(locale
, apple_language_locale
[i
].locale
))
219 strlcpy(language
, apple_language_locale
[i
].language
, sizeof(language
));
224 * Attempt to map the locale ID to a language ID...
227 if ((localeid
= CFStringCreateWithCString(kCFAllocatorDefault
, language
,
228 kCFStringEncodingASCII
)) != NULL
)
230 if ((langid
= CFLocaleCreateCanonicalLanguageIdentifierFromString(
231 kCFAllocatorDefault
, localeid
)) != NULL
)
233 CFStringGetCString(langid
, language
, (CFIndex
)langsize
, kCFStringEncodingASCII
);
241 * Return what we got...
249 * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID.
252 const char * /* O - Locale */
253 _cupsAppleLocale(CFStringRef languageName
, /* I - Apple language ID */
254 char *locale
, /* I - Buffer for locale */
255 size_t localesize
) /* I - Size of buffer */
257 int i
; /* Looping var */
258 CFStringRef localeName
; /* Locale as a CF string */
261 localeName
= CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault
, languageName
);
266 * Copy the locale name and tweak as needed...
269 if (!CFStringGetCString(localeName
, locale
, (CFIndex
)localesize
, kCFStringEncodingASCII
))
272 CFRelease(localeName
);
275 * Map new language identifiers to locales...
279 i
< (int)(sizeof(apple_language_locale
) /
280 sizeof(apple_language_locale
[0]));
283 size_t len
= strlen(apple_language_locale
[i
].language
);
285 if (!strcmp(locale
, apple_language_locale
[i
].language
) ||
286 (!strncmp(locale
, apple_language_locale
[i
].language
, len
) && (locale
[len
] == '_' || locale
[len
] == '-')))
288 strlcpy(locale
, apple_language_locale
[i
].locale
, localesize
);
296 * Just try the Apple language name...
299 if (!CFStringGetCString(languageName
, locale
, (CFIndex
)localesize
, kCFStringEncodingASCII
))
307 * Convert language subtag into region subtag...
310 if (locale
[2] == '-')
313 if (!strchr(locale
, '.'))
314 strlcat(locale
, ".UTF-8", localesize
);
318 #endif /* __APPLE__ */
322 * '_cupsEncodingName()' - Return the character encoding name string
323 * for the given encoding enumeration.
326 const char * /* O - Character encoding */
328 cups_encoding_t encoding
) /* I - Encoding value */
330 if (encoding
< CUPS_US_ASCII
||
331 encoding
>= (cups_encoding_t
)(sizeof(lang_encodings
) / sizeof(lang_encodings
[0])))
333 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
334 encoding
, lang_encodings
[0]));
335 return (lang_encodings
[0]);
339 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
340 encoding
, lang_encodings
[encoding
]));
341 return (lang_encodings
[encoding
]);
347 * 'cupsLangDefault()' - Return the default language.
350 cups_lang_t
* /* O - Language data */
351 cupsLangDefault(void)
353 return (cupsLangGet(NULL
));
358 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
359 * for the given language.
362 const char * /* O - Character encoding */
363 cupsLangEncoding(cups_lang_t
*lang
) /* I - Language data */
366 return ((char*)lang_encodings
[0]);
368 return ((char*)lang_encodings
[lang
->encoding
]);
373 * 'cupsLangFlush()' - Flush all language data out of the cache.
379 cups_lang_t
*lang
, /* Current language */
380 *next
; /* Next language */
384 * Free all languages in the cache...
387 _cupsMutexLock(&lang_mutex
);
389 for (lang
= lang_cache
; lang
!= NULL
; lang
= next
)
392 * Free all messages...
395 _cupsMessageFree(lang
->strings
);
398 * Then free the language structure itself...
407 _cupsMutexUnlock(&lang_mutex
);
412 * 'cupsLangFree()' - Free language data.
414 * This does not actually free anything; use @link cupsLangFlush@ for that.
418 cupsLangFree(cups_lang_t
*lang
) /* I - Language to free */
420 _cupsMutexLock(&lang_mutex
);
422 if (lang
!= NULL
&& lang
->used
> 0)
425 _cupsMutexUnlock(&lang_mutex
);
430 * 'cupsLangGet()' - Get a language.
433 cups_lang_t
* /* O - Language data */
434 cupsLangGet(const char *language
) /* I - Language or locale */
436 int i
; /* Looping var */
438 char locale
[255]; /* Copy of locale name */
439 #endif /* !__APPLE__ */
440 char langname
[16], /* Requested language name */
441 country
[16], /* Country code */
442 charset
[16], /* Character set */
443 *csptr
, /* Pointer to CODESET string */
444 *ptr
, /* Pointer into language/charset */
445 real
[48]; /* Real language name */
446 cups_encoding_t encoding
; /* Encoding to use */
447 cups_lang_t
*lang
; /* Current language... */
448 static const char * const locale_encodings
[] =
449 { /* Locale charset names */
450 "ASCII", "ISO88591", "ISO88592", "ISO88593",
451 "ISO88594", "ISO88595", "ISO88596", "ISO88597",
452 "ISO88598", "ISO88599", "ISO885910", "UTF8",
453 "ISO885913", "ISO885914", "ISO885915", "CP874",
454 "CP1250", "CP1251", "CP1252", "CP1253",
455 "CP1254", "CP1255", "CP1256", "CP1257",
456 "CP1258", "KOI8R", "KOI8U", "ISO885911",
457 "ISO885916", "MACROMAN", "", "",
468 "CP932", "CP936", "CP949", "CP950",
469 "CP1361", "", "", "",
486 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
491 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language
));
495 * Set the character set to UTF-8...
498 strlcpy(charset
, "UTF8", sizeof(charset
));
501 * Apple's setlocale doesn't give us the user's localization
502 * preference so we have to look it up this way...
507 if (!getenv("SOFTWARE") || (language
= getenv("LANG")) == NULL
)
508 language
= appleLangDefault();
510 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language
));
515 * Set the charset to "unknown"...
521 * Use setlocale() to determine the currently set locale, and then
522 * fallback to environment variables to avoid setting the locale,
523 * since setlocale() is not thread-safe!
529 * First see if the locale has been set; if it is still "C" or
530 * "POSIX", use the environment to get the default...
534 ptr
= setlocale(LC_MESSAGES
, NULL
);
536 ptr
= setlocale(LC_ALL
, NULL
);
537 # endif /* LC_MESSAGES */
539 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr
));
541 if (!ptr
|| !strcmp(ptr
, "C") || !strcmp(ptr
, "POSIX"))
544 * Get the character set from the LC_CTYPE locale setting...
547 if ((ptr
= getenv("LC_CTYPE")) == NULL
)
548 if ((ptr
= getenv("LC_ALL")) == NULL
)
549 if ((ptr
= getenv("LANG")) == NULL
)
552 if ((csptr
= strchr(ptr
, '.')) != NULL
)
555 * Extract the character set from the environment...
558 for (ptr
= charset
, csptr
++; *csptr
; csptr
++)
559 if (ptr
< (charset
+ sizeof(charset
) - 1) && _cups_isalnum(*csptr
))
566 * Get the locale for messages from the LC_MESSAGES locale setting...
569 if ((ptr
= getenv("LC_MESSAGES")) == NULL
)
570 if ((ptr
= getenv("LC_ALL")) == NULL
)
571 if ((ptr
= getenv("LANG")) == NULL
)
577 strlcpy(locale
, ptr
, sizeof(locale
));
581 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
584 if (!strncmp(locale
, "nb", 2))
587 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language
));
590 #endif /* __APPLE__ */
593 * If "language" is NULL at this point, then chances are we are using
594 * a language that is not installed for the base OS.
600 * Switch to the POSIX ("C") locale...
608 * On systems that support the nl_langinfo(CODESET) call, use
609 * this value as the character set...
612 if (!charset
[0] && (csptr
= nl_langinfo(CODESET
)) != NULL
)
615 * Copy all of the letters and numbers in the CODESET string...
618 for (ptr
= charset
; *csptr
; csptr
++)
619 if (_cups_isalnum(*csptr
) && ptr
< (charset
+ sizeof(charset
) - 1))
624 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
625 "nl_langinfo(CODESET)...", charset
));
630 * If we don't have a character set by now, default to UTF-8...
634 strlcpy(charset
, "UTF8", sizeof(charset
));
637 * Parse the language string passed in to a locale string. "C" is the
638 * standard POSIX locale and is copied unchanged. Otherwise the
639 * language string is converted from ll-cc[.charset] (language-country)
640 * to ll_CC[.CHARSET] to match the file naming convention used by all
641 * POSIX-compliant operating systems. Invalid language names are mapped
642 * to the POSIX locale.
647 if (language
== NULL
|| !language
[0] ||
648 !strcmp(language
, "POSIX"))
649 strlcpy(langname
, "C", sizeof(langname
));
653 * Copy the parts of the locale string over safely...
656 for (ptr
= langname
; *language
; language
++)
657 if (*language
== '_' || *language
== '-' || *language
== '.')
659 else if (ptr
< (langname
+ sizeof(langname
) - 1))
660 *ptr
++ = (char)tolower(*language
& 255);
664 if (*language
== '_' || *language
== '-')
667 * Copy the country code...
670 for (language
++, ptr
= country
; *language
; language
++)
671 if (*language
== '.')
673 else if (ptr
< (country
+ sizeof(country
) - 1))
674 *ptr
++ = (char)toupper(*language
& 255);
679 if (*language
== '.' && !charset
[0])
682 * Copy the encoding...
685 for (language
++, ptr
= charset
; *language
; language
++)
686 if (_cups_isalnum(*language
) && ptr
< (charset
+ sizeof(charset
) - 1))
687 *ptr
++ = (char)toupper(*language
& 255);
693 * Force a POSIX locale for an invalid language name...
696 if (strlen(langname
) != 2)
698 strlcpy(langname
, "C", sizeof(langname
));
704 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
705 langname
, country
, charset
));
708 * Figure out the desired encoding...
711 encoding
= CUPS_AUTO_ENCODING
;
716 i
< (int)(sizeof(locale_encodings
) / sizeof(locale_encodings
[0]));
718 if (!_cups_strcasecmp(charset
, locale_encodings
[i
]))
720 encoding
= (cups_encoding_t
)i
;
724 if (encoding
== CUPS_AUTO_ENCODING
)
727 * Map alternate names for various character sets...
730 if (!_cups_strcasecmp(charset
, "iso-2022-jp") ||
731 !_cups_strcasecmp(charset
, "sjis"))
732 encoding
= CUPS_WINDOWS_932
;
733 else if (!_cups_strcasecmp(charset
, "iso-2022-cn"))
734 encoding
= CUPS_WINDOWS_936
;
735 else if (!_cups_strcasecmp(charset
, "iso-2022-kr"))
736 encoding
= CUPS_WINDOWS_949
;
737 else if (!_cups_strcasecmp(charset
, "big5"))
738 encoding
= CUPS_WINDOWS_950
;
742 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding
,
743 encoding
== CUPS_AUTO_ENCODING
? "auto" :
744 lang_encodings
[encoding
]));
747 * See if we already have this language/country loaded...
751 snprintf(real
, sizeof(real
), "%s_%s", langname
, country
);
753 strlcpy(real
, langname
, sizeof(real
));
755 _cupsMutexLock(&lang_mutex
);
757 if ((lang
= cups_cache_lookup(real
, encoding
)) != NULL
)
759 _cupsMutexUnlock(&lang_mutex
);
761 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real
));
767 * See if there is a free language available; if so, use that
771 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
778 * Allocate memory for the language and add it to the cache.
781 if ((lang
= calloc(sizeof(cups_lang_t
), 1)) == NULL
)
783 _cupsMutexUnlock(&lang_mutex
);
788 lang
->next
= lang_cache
;
794 * Free all old strings as needed...
797 _cupsMessageFree(lang
->strings
);
798 lang
->strings
= NULL
;
802 * Then assign the language and encoding fields...
806 strlcpy(lang
->language
, real
, sizeof(lang
->language
));
808 if (encoding
!= CUPS_AUTO_ENCODING
)
809 lang
->encoding
= encoding
;
811 lang
->encoding
= CUPS_UTF8
;
817 _cupsMutexUnlock(&lang_mutex
);
824 * '_cupsLangString()' - Get a message string.
826 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
827 * convert the string to the language encoding.
830 const char * /* O - Localized message */
831 _cupsLangString(cups_lang_t
*lang
, /* I - Language */
832 const char *message
) /* I - Message */
834 const char *s
; /* Localized message */
837 * Range check input...
840 if (!lang
|| !message
|| !*message
)
843 _cupsMutexLock(&lang_mutex
);
846 * Load the message catalog if needed...
850 cups_message_load(lang
);
852 s
= _cupsMessageLookup(lang
->strings
, message
);
854 _cupsMutexUnlock(&lang_mutex
);
861 * '_cupsMessageFree()' - Free a messages array.
865 _cupsMessageFree(cups_array_t
*a
) /* I - Message array */
867 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
869 * Release the cups.strings dictionary as needed...
872 if (cupsArrayUserData(a
))
873 CFRelease((CFDictionaryRef
)cupsArrayUserData(a
));
874 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
885 * '_cupsMessageLoad()' - Load a .po file into a messages array.
888 cups_array_t
* /* O - New message array */
889 _cupsMessageLoad(const char *filename
, /* I - Message catalog to load */
890 int unquote
) /* I - Unescape \foo in strings? */
892 cups_file_t
*fp
; /* Message file */
893 cups_array_t
*a
; /* Message array */
894 _cups_message_t
*m
; /* Current message */
895 char s
[4096], /* String buffer */
896 *ptr
, /* Pointer into buffer */
897 *temp
; /* New string */
898 size_t length
, /* Length of combined strings */
899 ptrlen
; /* Length of string */
902 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename
));
905 * Create an array to hold the messages...
908 if ((a
= _cupsMessageNew(NULL
)) == NULL
)
910 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
915 * Open the message catalog file...
918 if ((fp
= cupsFileOpen(filename
, "r")) == NULL
)
920 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
926 * Read messages from the catalog file until EOF...
928 * The format is the GNU gettext .po format, which is fairly simple:
931 * msgstr "localized text"
933 * The ID and localized text can span multiple lines using the form:
938 * "localized text spanning "
944 while (cupsFileGets(fp
, s
, sizeof(s
)) != NULL
)
947 * Skip blank and comment lines...
950 if (s
[0] == '#' || !s
[0])
954 * Strip the trailing quote...
957 if ((ptr
= strrchr(s
, '\"')) == NULL
)
963 * Find start of value...
966 if ((ptr
= strchr(s
, '\"')) == NULL
)
972 * Unquote the text...
976 cups_unquote(ptr
, ptr
);
979 * Create or add to a message...
982 if (!strncmp(s
, "msgid", 5))
985 * Add previous message as needed...
990 if (m
->str
&& m
->str
[0])
997 * Translation is empty, don't add it... (STR #4033)
1008 * Create a new message with the given msgid string...
1011 if ((m
= (_cups_message_t
*)calloc(1, sizeof(_cups_message_t
))) == NULL
)
1017 if ((m
->id
= strdup(ptr
)) == NULL
)
1024 else if (s
[0] == '\"' && m
)
1027 * Append to current string...
1030 length
= strlen(m
->str
? m
->str
: m
->id
);
1031 ptrlen
= strlen(ptr
);
1033 if ((temp
= realloc(m
->str
? m
->str
: m
->id
, length
+ ptrlen
+ 1)) == NULL
)
1047 * Copy the new portion to the end of the msgstr string - safe
1048 * to use memcpy because the buffer is allocated to the correct
1054 memcpy(m
->str
+ length
, ptr
, ptrlen
+ 1);
1059 * Copy the new portion to the end of the msgid string - safe
1060 * to use memcpy because the buffer is allocated to the correct
1066 memcpy(m
->id
+ length
, ptr
, ptrlen
+ 1);
1069 else if (!strncmp(s
, "msgstr", 6) && m
)
1075 if ((m
->str
= strdup(ptr
)) == NULL
)
1087 * Add the last message string to the array as needed...
1092 if (m
->str
&& m
->str
[0])
1099 * Translation is empty, don't add it... (STR #4033)
1110 * Close the message catalog file and return the new array...
1115 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
1116 cupsArrayCount(a
)));
1123 * '_cupsMessageLookup()' - Lookup a message string.
1126 const char * /* O - Localized message */
1127 _cupsMessageLookup(cups_array_t
*a
, /* I - Message array */
1128 const char *m
) /* I - Message */
1130 _cups_message_t key
, /* Search key */
1131 *match
; /* Matching message */
1135 * Lookup the message string; if it doesn't exist in the catalog,
1136 * then return the message that was passed to us...
1140 match
= (_cups_message_t
*)cupsArrayFind(a
, &key
);
1142 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1143 if (!match
&& cupsArrayUserData(a
))
1146 * Try looking the string up in the cups.strings dictionary...
1149 CFDictionaryRef dict
; /* cups.strings dictionary */
1150 CFStringRef cfm
, /* Message as a CF string */
1151 cfstr
; /* Localized text as a CF string */
1153 dict
= (CFDictionaryRef
)cupsArrayUserData(a
);
1154 cfm
= CFStringCreateWithCString(kCFAllocatorDefault
, m
,
1155 kCFStringEncodingUTF8
);
1156 match
= calloc(1, sizeof(_cups_message_t
));
1157 match
->id
= strdup(m
);
1158 cfstr
= cfm
? CFDictionaryGetValue(dict
, cfm
) : NULL
;
1162 char buffer
[1024]; /* Message buffer */
1164 CFStringGetCString(cfstr
, buffer
, sizeof(buffer
), kCFStringEncodingUTF8
);
1165 match
->str
= strdup(buffer
);
1167 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...",
1172 match
->str
= strdup(m
);
1174 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m
));
1177 cupsArrayAdd(a
, match
);
1182 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1184 if (match
&& match
->str
)
1185 return (match
->str
);
1192 * '_cupsMessageNew()' - Make a new message catalog array.
1195 cups_array_t
* /* O - Array */
1196 _cupsMessageNew(void *context
) /* I - User data */
1198 return (cupsArrayNew3((cups_array_func_t
)cups_message_compare
, context
,
1199 (cups_ahash_func_t
)NULL
, 0,
1200 (cups_acopy_func_t
)NULL
,
1201 (cups_afree_func_t
)cups_message_free
));
1207 * 'appleLangDefault()' - Get the default locale string.
1210 static const char * /* O - Locale string */
1211 appleLangDefault(void)
1213 CFBundleRef bundle
; /* Main bundle (if any) */
1214 CFArrayRef bundleList
; /* List of localizations in bundle */
1215 CFPropertyListRef localizationList
= NULL
;
1216 /* List of localization data */
1217 CFStringRef languageName
; /* Current name */
1218 char *lang
; /* LANG environment variable */
1219 _cups_globals_t
*cg
= _cupsGlobals();
1220 /* Pointer to library globals */
1223 DEBUG_puts("2appleLangDefault()");
1226 * Only do the lookup and translation the first time.
1229 if (!cg
->language
[0])
1231 if (getenv("SOFTWARE") != NULL
&& (lang
= getenv("LANG")) != NULL
)
1233 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang
));
1234 strlcpy(cg
->language
, lang
, sizeof(cg
->language
));
1235 return (cg
->language
);
1237 else if ((bundle
= CFBundleGetMainBundle()) != NULL
&&
1238 (bundleList
= CFBundleCopyBundleLocalizations(bundle
)) != NULL
)
1240 CFURLRef resources
= CFBundleCopyResourcesDirectoryURL(bundle
);
1242 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
1246 CFStringRef cfpath
= CFURLCopyPath(resources
);
1252 * See if we have an Info.plist file in the bundle...
1255 CFStringGetCString(cfpath
, path
, sizeof(path
), kCFStringEncodingUTF8
);
1256 DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path
));
1257 strlcat(path
, "Contents/Info.plist", sizeof(path
));
1259 if (!access(path
, R_OK
))
1260 localizationList
= CFBundleCopyPreferredLocalizationsFromArray(bundleList
);
1262 DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL...");
1267 CFRelease(resources
);
1270 DEBUG_puts("3appleLangDefault: No resource URL.");
1272 CFRelease(bundleList
);
1275 if (!localizationList
)
1277 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
1280 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1281 kCFPreferencesCurrentApplication
);
1284 if (localizationList
)
1287 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID())
1288 DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.",
1289 (int)CFArrayGetCount(localizationList
)));
1291 DEBUG_puts("3appleLangDefault: Got localizationList but not an array.");
1294 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID() &&
1295 CFArrayGetCount(localizationList
) > 0)
1297 languageName
= CFArrayGetValueAtIndex(localizationList
, 0);
1300 CFGetTypeID(languageName
) == CFStringGetTypeID())
1302 if (_cupsAppleLocale(languageName
, cg
->language
, sizeof(cg
->language
)))
1303 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
1306 DEBUG_puts("3appleLangDefault: Unable to get locale.");
1310 CFRelease(localizationList
);
1314 * If we didn't find the language, default to en_US...
1317 if (!cg
->language
[0])
1319 DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
1320 strlcpy(cg
->language
, "en_US.UTF-8", sizeof(cg
->language
));
1324 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg
->language
));
1327 * Return the cached locale...
1330 return (cg
->language
);
1334 # ifdef CUPS_BUNDLEDIR
1336 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1339 static cups_array_t
* /* O - Message catalog */
1340 appleMessageLoad(const char *locale
) /* I - Locale ID */
1342 char filename
[1024], /* Path to cups.strings file */
1343 applelang
[256], /* Apple language ID */
1344 baselang
[3]; /* Base language */
1345 CFURLRef url
; /* URL to cups.strings file */
1346 CFReadStreamRef stream
= NULL
; /* File stream */
1347 CFPropertyListRef plist
= NULL
; /* Localization file */
1349 CFErrorRef error
= NULL
; /* Error when opening file */
1353 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale
));
1356 * Load the cups.strings file...
1359 snprintf(filename
, sizeof(filename
),
1360 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings",
1361 _cupsAppleLanguage(locale
, applelang
, sizeof(applelang
)));
1363 if (access(filename
, 0))
1366 * <rdar://problem/22086642>
1368 * Try with original locale string...
1371 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1374 if (access(filename
, 0))
1377 * <rdar://problem/25292403>
1379 * Try with just the language code...
1382 strlcpy(baselang
, locale
, sizeof(baselang
));
1383 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", baselang
);
1386 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename
));
1388 if (access(filename
, 0))
1391 * Try alternate lproj directory names...
1394 if (!strncmp(locale
, "en", 2))
1396 else if (!strncmp(locale
, "nb", 2))
1398 else if (!strncmp(locale
, "nl", 2))
1400 else if (!strncmp(locale
, "fr", 2))
1402 else if (!strncmp(locale
, "de", 2))
1404 else if (!strncmp(locale
, "it", 2))
1406 else if (!strncmp(locale
, "ja", 2))
1407 locale
= "Japanese";
1408 else if (!strncmp(locale
, "es", 2))
1410 else if (!strcmp(locale
, "zh_HK") || !strncmp(locale
, "zh-Hant", 7))
1413 * <rdar://problem/22130168>
1414 * <rdar://problem/27245567>
1416 * Try zh_TW first, then zh... Sigh...
1419 if (!access(CUPS_BUNDLEDIR
"/Resources/zh_TW.lproj/cups.strings", 0))
1424 else if (strstr(locale
, "_") != NULL
|| strstr(locale
, "-") != NULL
)
1427 * Drop country code, just try language...
1430 strlcpy(baselang
, locale
, sizeof(baselang
));
1434 snprintf(filename
, sizeof(filename
),
1435 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1436 DEBUG_printf(("1appleMessageLoad: alternate filename=\"%s\"", filename
));
1439 url
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
,
1441 (CFIndex
)strlen(filename
), false);
1444 stream
= CFReadStreamCreateWithFile(kCFAllocatorDefault
, url
);
1448 * Read the property list containing the localization data.
1450 * NOTE: This code currently generates a clang "potential leak"
1451 * warning, but the object is released in _cupsMessageFree().
1454 CFReadStreamOpen(stream
);
1457 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1458 kCFPropertyListImmutable
, NULL
,
1462 CFStringRef msg
= CFErrorCopyDescription(error
);
1465 CFStringGetCString(msg
, filename
, sizeof(filename
),
1466 kCFStringEncodingUTF8
);
1467 DEBUG_printf(("1appleMessageLoad: %s", filename
));
1474 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1475 kCFPropertyListImmutable
, NULL
,
1479 if (plist
&& CFGetTypeID(plist
) != CFDictionaryGetTypeID())
1491 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url
, stream
,
1495 * Create and return an empty array to act as a cache for messages, passing the
1496 * plist as the user data.
1499 return (_cupsMessageNew((void *)plist
));
1501 # endif /* CUPS_BUNDLEDIR */
1502 #endif /* __APPLE__ */
1506 * 'cups_cache_lookup()' - Lookup a language in the cache...
1509 static cups_lang_t
* /* O - Language data or NULL */
1511 const char *name
, /* I - Name of locale */
1512 cups_encoding_t encoding
) /* I - Encoding of locale */
1514 cups_lang_t
*lang
; /* Current language */
1517 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name
,
1518 encoding
, encoding
== CUPS_AUTO_ENCODING
? "auto" :
1519 lang_encodings
[encoding
]));
1522 * Loop through the cache and return a match if found...
1525 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
1527 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1528 "encoding=%d(%s)", (void *)lang
, lang
->language
, lang
->encoding
,
1529 lang_encodings
[lang
->encoding
]));
1531 if (!strcmp(lang
->language
, name
) &&
1532 (encoding
== CUPS_AUTO_ENCODING
|| encoding
== lang
->encoding
))
1536 DEBUG_puts("8cups_cache_lookup: returning match!");
1542 DEBUG_puts("8cups_cache_lookup: returning NULL!");
1549 * 'cups_message_compare()' - Compare two messages.
1552 static int /* O - Result of comparison */
1553 cups_message_compare(
1554 _cups_message_t
*m1
, /* I - First message */
1555 _cups_message_t
*m2
) /* I - Second message */
1557 return (strcmp(m1
->id
, m2
->id
));
1562 * 'cups_message_free()' - Free a message.
1566 cups_message_free(_cups_message_t
*m
) /* I - Message */
1579 * 'cups_message_load()' - Load the message catalog for a language.
1583 cups_message_load(cups_lang_t
*lang
) /* I - Language */
1585 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1586 lang
->strings
= appleMessageLoad(lang
->language
);
1589 char filename
[1024]; /* Filename for language locale file */
1590 _cups_globals_t
*cg
= _cupsGlobals();
1591 /* Pointer to library globals */
1594 snprintf(filename
, sizeof(filename
), "%s/%s/cups_%s.po", cg
->localedir
,
1595 lang
->language
, lang
->language
);
1597 if (strchr(lang
->language
, '_') && access(filename
, 0))
1600 * Country localization not available, look for generic localization...
1603 snprintf(filename
, sizeof(filename
), "%s/%.2s/cups_%.2s.po", cg
->localedir
,
1604 lang
->language
, lang
->language
);
1606 if (access(filename
, 0))
1609 * No generic localization, so use POSIX...
1612 DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename
,
1615 snprintf(filename
, sizeof(filename
), "%s/C/cups_C.po", cg
->localedir
);
1620 * Read the strings from the file...
1623 lang
->strings
= _cupsMessageLoad(filename
, 1);
1624 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1629 * 'cups_unquote()' - Unquote characters in strings...
1633 cups_unquote(char *d
, /* O - Unquoted string */
1634 const char *s
) /* I - Original string */
1647 *d
= *d
* 8 + *s
- '0';