2 * I18N/language support for CUPS.
4 * Copyright 2007-2016 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-Hant", "zh_TW" },
127 { "zh-Hant_CN", "zh_TW" }
129 #endif /* __APPLE__ */
138 static const char *appleLangDefault(void);
139 # ifdef CUPS_BUNDLEDIR
140 # ifndef CF_RETURNS_RETAINED
141 # if __has_feature(attribute_cf_returns_retained)
142 # define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
144 # define CF_RETURNS_RETAINED
145 # endif /* __has_feature(attribute_cf_returns_retained) */
146 # endif /* !CF_RETURNED_RETAINED */
147 static cups_array_t
*appleMessageLoad(const char *locale
)
149 # endif /* CUPS_BUNDLEDIR */
150 #endif /* __APPLE__ */
151 static cups_lang_t
*cups_cache_lookup(const char *name
,
152 cups_encoding_t encoding
);
153 static int cups_message_compare(_cups_message_t
*m1
,
154 _cups_message_t
*m2
);
155 static void cups_message_free(_cups_message_t
*m
);
156 static void cups_message_load(cups_lang_t
*lang
);
157 static void cups_unquote(char *d
, const char *s
);
162 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
166 const char * /* O - Language ID */
167 _cupsAppleLanguage(const char *locale
, /* I - Locale ID */
168 char *language
,/* I - Language ID buffer */
169 size_t langsize
) /* I - Size of language ID buffer */
171 int i
; /* Looping var */
172 CFStringRef localeid
, /* CF locale identifier */
173 langid
; /* CF language identifier */
177 * Copy the locale name and convert, as needed, to the Apple-specific
178 * locale identifier...
181 switch (strlen(locale
))
188 strlcpy(language
, "en", langsize
);
192 strlcpy(language
, locale
, langsize
);
196 strlcpy(language
, locale
, langsize
);
198 if (language
[2] == '-')
201 * Convert ll-cc to ll_CC...
205 language
[3] = (char)toupper(language
[3] & 255);
206 language
[4] = (char)toupper(language
[4] & 255);
212 i
< (int)(sizeof(apple_language_locale
) /
213 sizeof(apple_language_locale
[0]));
215 if (!strcmp(locale
, apple_language_locale
[i
].locale
))
217 strlcpy(language
, apple_language_locale
[i
].language
, sizeof(language
));
222 * Attempt to map the locale ID to a language ID...
225 if ((localeid
= CFStringCreateWithCString(kCFAllocatorDefault
, language
,
226 kCFStringEncodingASCII
)) != NULL
)
228 if ((langid
= CFLocaleCreateCanonicalLanguageIdentifierFromString(
229 kCFAllocatorDefault
, localeid
)) != NULL
)
231 CFStringGetCString(langid
, language
, (CFIndex
)langsize
, kCFStringEncodingASCII
);
239 * Return what we got...
247 * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID.
250 const char * /* O - Locale */
251 _cupsAppleLocale(CFStringRef languageName
, /* I - Apple language ID */
252 char *locale
, /* I - Buffer for locale */
253 size_t localesize
) /* I - Size of buffer */
255 int i
; /* Looping var */
256 CFStringRef localeName
; /* Locale as a CF string */
259 localeName
= CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault
, languageName
);
264 * Copy the locale name and tweak as needed...
267 if (!CFStringGetCString(localeName
, locale
, (CFIndex
)localesize
, kCFStringEncodingASCII
))
270 CFRelease(localeName
);
273 * Map new language identifiers to locales...
277 i
< (int)(sizeof(apple_language_locale
) /
278 sizeof(apple_language_locale
[0]));
281 if (!strcmp(locale
, apple_language_locale
[i
].language
))
283 strlcpy(locale
, apple_language_locale
[i
].locale
, localesize
);
291 * Just try the Apple language name...
294 if (!CFStringGetCString(languageName
, locale
, (CFIndex
)localesize
, kCFStringEncodingASCII
))
302 * Convert language subtag into region subtag...
305 if (locale
[2] == '-')
308 if (!strchr(locale
, '.'))
309 strlcat(locale
, ".UTF-8", localesize
);
313 #endif /* __APPLE__ */
317 * '_cupsEncodingName()' - Return the character encoding name string
318 * for the given encoding enumeration.
321 const char * /* O - Character encoding */
323 cups_encoding_t encoding
) /* I - Encoding value */
325 if (encoding
< CUPS_US_ASCII
||
326 encoding
>= (cups_encoding_t
)(sizeof(lang_encodings
) / sizeof(lang_encodings
[0])))
328 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
329 encoding
, lang_encodings
[0]));
330 return (lang_encodings
[0]);
334 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
335 encoding
, lang_encodings
[encoding
]));
336 return (lang_encodings
[encoding
]);
342 * 'cupsLangDefault()' - Return the default language.
345 cups_lang_t
* /* O - Language data */
346 cupsLangDefault(void)
348 return (cupsLangGet(NULL
));
353 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
354 * for the given language.
357 const char * /* O - Character encoding */
358 cupsLangEncoding(cups_lang_t
*lang
) /* I - Language data */
361 return ((char*)lang_encodings
[0]);
363 return ((char*)lang_encodings
[lang
->encoding
]);
368 * 'cupsLangFlush()' - Flush all language data out of the cache.
374 cups_lang_t
*lang
, /* Current language */
375 *next
; /* Next language */
379 * Free all languages in the cache...
382 _cupsMutexLock(&lang_mutex
);
384 for (lang
= lang_cache
; lang
!= NULL
; lang
= next
)
387 * Free all messages...
390 _cupsMessageFree(lang
->strings
);
393 * Then free the language structure itself...
402 _cupsMutexUnlock(&lang_mutex
);
407 * 'cupsLangFree()' - Free language data.
409 * This does not actually free anything; use @link cupsLangFlush@ for that.
413 cupsLangFree(cups_lang_t
*lang
) /* I - Language to free */
415 _cupsMutexLock(&lang_mutex
);
417 if (lang
!= NULL
&& lang
->used
> 0)
420 _cupsMutexUnlock(&lang_mutex
);
425 * 'cupsLangGet()' - Get a language.
428 cups_lang_t
* /* O - Language data */
429 cupsLangGet(const char *language
) /* I - Language or locale */
431 int i
; /* Looping var */
433 char locale
[255]; /* Copy of locale name */
434 #endif /* !__APPLE__ */
435 char langname
[16], /* Requested language name */
436 country
[16], /* Country code */
437 charset
[16], /* Character set */
438 *csptr
, /* Pointer to CODESET string */
439 *ptr
, /* Pointer into language/charset */
440 real
[48]; /* Real language name */
441 cups_encoding_t encoding
; /* Encoding to use */
442 cups_lang_t
*lang
; /* Current language... */
443 static const char * const locale_encodings
[] =
444 { /* Locale charset names */
445 "ASCII", "ISO88591", "ISO88592", "ISO88593",
446 "ISO88594", "ISO88595", "ISO88596", "ISO88597",
447 "ISO88598", "ISO88599", "ISO885910", "UTF8",
448 "ISO885913", "ISO885914", "ISO885915", "CP874",
449 "CP1250", "CP1251", "CP1252", "CP1253",
450 "CP1254", "CP1255", "CP1256", "CP1257",
451 "CP1258", "KOI8R", "KOI8U", "ISO885911",
452 "ISO885916", "MACROMAN", "", "",
463 "CP932", "CP936", "CP949", "CP950",
464 "CP1361", "", "", "",
481 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
486 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language
));
490 * Set the character set to UTF-8...
493 strlcpy(charset
, "UTF8", sizeof(charset
));
496 * Apple's setlocale doesn't give us the user's localization
497 * preference so we have to look it up this way...
502 if (!getenv("SOFTWARE") || (language
= getenv("LANG")) == NULL
)
503 language
= appleLangDefault();
505 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language
));
510 * Set the charset to "unknown"...
516 * Use setlocale() to determine the currently set locale, and then
517 * fallback to environment variables to avoid setting the locale,
518 * since setlocale() is not thread-safe!
524 * First see if the locale has been set; if it is still "C" or
525 * "POSIX", use the environment to get the default...
529 ptr
= setlocale(LC_MESSAGES
, NULL
);
531 ptr
= setlocale(LC_ALL
, NULL
);
532 # endif /* LC_MESSAGES */
534 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr
));
536 if (!ptr
|| !strcmp(ptr
, "C") || !strcmp(ptr
, "POSIX"))
539 * Get the character set from the LC_CTYPE locale setting...
542 if ((ptr
= getenv("LC_CTYPE")) == NULL
)
543 if ((ptr
= getenv("LC_ALL")) == NULL
)
544 if ((ptr
= getenv("LANG")) == NULL
)
547 if ((csptr
= strchr(ptr
, '.')) != NULL
)
550 * Extract the character set from the environment...
553 for (ptr
= charset
, csptr
++; *csptr
; csptr
++)
554 if (ptr
< (charset
+ sizeof(charset
) - 1) && _cups_isalnum(*csptr
))
561 * Get the locale for messages from the LC_MESSAGES locale setting...
564 if ((ptr
= getenv("LC_MESSAGES")) == NULL
)
565 if ((ptr
= getenv("LC_ALL")) == NULL
)
566 if ((ptr
= getenv("LANG")) == NULL
)
572 strlcpy(locale
, ptr
, sizeof(locale
));
576 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
579 if (!strncmp(locale
, "nb", 2))
582 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language
));
585 #endif /* __APPLE__ */
588 * If "language" is NULL at this point, then chances are we are using
589 * a language that is not installed for the base OS.
595 * Switch to the POSIX ("C") locale...
603 * On systems that support the nl_langinfo(CODESET) call, use
604 * this value as the character set...
607 if (!charset
[0] && (csptr
= nl_langinfo(CODESET
)) != NULL
)
610 * Copy all of the letters and numbers in the CODESET string...
613 for (ptr
= charset
; *csptr
; csptr
++)
614 if (_cups_isalnum(*csptr
) && ptr
< (charset
+ sizeof(charset
) - 1))
619 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
620 "nl_langinfo(CODESET)...", charset
));
625 * If we don't have a character set by now, default to UTF-8...
629 strlcpy(charset
, "UTF8", sizeof(charset
));
632 * Parse the language string passed in to a locale string. "C" is the
633 * standard POSIX locale and is copied unchanged. Otherwise the
634 * language string is converted from ll-cc[.charset] (language-country)
635 * to ll_CC[.CHARSET] to match the file naming convention used by all
636 * POSIX-compliant operating systems. Invalid language names are mapped
637 * to the POSIX locale.
642 if (language
== NULL
|| !language
[0] ||
643 !strcmp(language
, "POSIX"))
644 strlcpy(langname
, "C", sizeof(langname
));
648 * Copy the parts of the locale string over safely...
651 for (ptr
= langname
; *language
; language
++)
652 if (*language
== '_' || *language
== '-' || *language
== '.')
654 else if (ptr
< (langname
+ sizeof(langname
) - 1))
655 *ptr
++ = (char)tolower(*language
& 255);
659 if (*language
== '_' || *language
== '-')
662 * Copy the country code...
665 for (language
++, ptr
= country
; *language
; language
++)
666 if (*language
== '.')
668 else if (ptr
< (country
+ sizeof(country
) - 1))
669 *ptr
++ = (char)toupper(*language
& 255);
674 if (*language
== '.' && !charset
[0])
677 * Copy the encoding...
680 for (language
++, ptr
= charset
; *language
; language
++)
681 if (_cups_isalnum(*language
) && ptr
< (charset
+ sizeof(charset
) - 1))
682 *ptr
++ = (char)toupper(*language
& 255);
688 * Force a POSIX locale for an invalid language name...
691 if (strlen(langname
) != 2)
693 strlcpy(langname
, "C", sizeof(langname
));
699 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
700 langname
, country
, charset
));
703 * Figure out the desired encoding...
706 encoding
= CUPS_AUTO_ENCODING
;
711 i
< (int)(sizeof(locale_encodings
) / sizeof(locale_encodings
[0]));
713 if (!_cups_strcasecmp(charset
, locale_encodings
[i
]))
715 encoding
= (cups_encoding_t
)i
;
719 if (encoding
== CUPS_AUTO_ENCODING
)
722 * Map alternate names for various character sets...
725 if (!_cups_strcasecmp(charset
, "iso-2022-jp") ||
726 !_cups_strcasecmp(charset
, "sjis"))
727 encoding
= CUPS_WINDOWS_932
;
728 else if (!_cups_strcasecmp(charset
, "iso-2022-cn"))
729 encoding
= CUPS_WINDOWS_936
;
730 else if (!_cups_strcasecmp(charset
, "iso-2022-kr"))
731 encoding
= CUPS_WINDOWS_949
;
732 else if (!_cups_strcasecmp(charset
, "big5"))
733 encoding
= CUPS_WINDOWS_950
;
737 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding
,
738 encoding
== CUPS_AUTO_ENCODING
? "auto" :
739 lang_encodings
[encoding
]));
742 * See if we already have this language/country loaded...
746 snprintf(real
, sizeof(real
), "%s_%s", langname
, country
);
748 strlcpy(real
, langname
, sizeof(real
));
750 _cupsMutexLock(&lang_mutex
);
752 if ((lang
= cups_cache_lookup(real
, encoding
)) != NULL
)
754 _cupsMutexUnlock(&lang_mutex
);
756 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real
));
762 * See if there is a free language available; if so, use that
766 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
773 * Allocate memory for the language and add it to the cache.
776 if ((lang
= calloc(sizeof(cups_lang_t
), 1)) == NULL
)
778 _cupsMutexUnlock(&lang_mutex
);
783 lang
->next
= lang_cache
;
789 * Free all old strings as needed...
792 _cupsMessageFree(lang
->strings
);
793 lang
->strings
= NULL
;
797 * Then assign the language and encoding fields...
801 strlcpy(lang
->language
, real
, sizeof(lang
->language
));
803 if (encoding
!= CUPS_AUTO_ENCODING
)
804 lang
->encoding
= encoding
;
806 lang
->encoding
= CUPS_UTF8
;
812 _cupsMutexUnlock(&lang_mutex
);
819 * '_cupsLangString()' - Get a message string.
821 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
822 * convert the string to the language encoding.
825 const char * /* O - Localized message */
826 _cupsLangString(cups_lang_t
*lang
, /* I - Language */
827 const char *message
) /* I - Message */
829 const char *s
; /* Localized message */
832 * Range check input...
835 if (!lang
|| !message
|| !*message
)
838 _cupsMutexLock(&lang_mutex
);
841 * Load the message catalog if needed...
845 cups_message_load(lang
);
847 s
= _cupsMessageLookup(lang
->strings
, message
);
849 _cupsMutexUnlock(&lang_mutex
);
856 * '_cupsMessageFree()' - Free a messages array.
860 _cupsMessageFree(cups_array_t
*a
) /* I - Message array */
862 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
864 * Release the cups.strings dictionary as needed...
867 if (cupsArrayUserData(a
))
868 CFRelease((CFDictionaryRef
)cupsArrayUserData(a
));
869 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
880 * '_cupsMessageLoad()' - Load a .po file into a messages array.
883 cups_array_t
* /* O - New message array */
884 _cupsMessageLoad(const char *filename
, /* I - Message catalog to load */
885 int unquote
) /* I - Unescape \foo in strings? */
887 cups_file_t
*fp
; /* Message file */
888 cups_array_t
*a
; /* Message array */
889 _cups_message_t
*m
; /* Current message */
890 char s
[4096], /* String buffer */
891 *ptr
, /* Pointer into buffer */
892 *temp
; /* New string */
893 size_t length
, /* Length of combined strings */
894 ptrlen
; /* Length of string */
897 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename
));
900 * Create an array to hold the messages...
903 if ((a
= _cupsMessageNew(NULL
)) == NULL
)
905 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
910 * Open the message catalog file...
913 if ((fp
= cupsFileOpen(filename
, "r")) == NULL
)
915 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
921 * Read messages from the catalog file until EOF...
923 * The format is the GNU gettext .po format, which is fairly simple:
926 * msgstr "localized text"
928 * The ID and localized text can span multiple lines using the form:
933 * "localized text spanning "
939 while (cupsFileGets(fp
, s
, sizeof(s
)) != NULL
)
942 * Skip blank and comment lines...
945 if (s
[0] == '#' || !s
[0])
949 * Strip the trailing quote...
952 if ((ptr
= strrchr(s
, '\"')) == NULL
)
958 * Find start of value...
961 if ((ptr
= strchr(s
, '\"')) == NULL
)
967 * Unquote the text...
971 cups_unquote(ptr
, ptr
);
974 * Create or add to a message...
977 if (!strncmp(s
, "msgid", 5))
980 * Add previous message as needed...
985 if (m
->str
&& m
->str
[0])
992 * Translation is empty, don't add it... (STR #4033)
1003 * Create a new message with the given msgid string...
1006 if ((m
= (_cups_message_t
*)calloc(1, sizeof(_cups_message_t
))) == NULL
)
1012 if ((m
->id
= strdup(ptr
)) == NULL
)
1019 else if (s
[0] == '\"' && m
)
1022 * Append to current string...
1025 length
= strlen(m
->str
? m
->str
: m
->id
);
1026 ptrlen
= strlen(ptr
);
1028 if ((temp
= realloc(m
->str
? m
->str
: m
->id
, length
+ ptrlen
+ 1)) == NULL
)
1042 * Copy the new portion to the end of the msgstr string - safe
1043 * to use memcpy because the buffer is allocated to the correct
1049 memcpy(m
->str
+ length
, ptr
, ptrlen
+ 1);
1054 * Copy the new portion to the end of the msgid string - safe
1055 * to use memcpy because the buffer is allocated to the correct
1061 memcpy(m
->id
+ length
, ptr
, ptrlen
+ 1);
1064 else if (!strncmp(s
, "msgstr", 6) && m
)
1070 if ((m
->str
= strdup(ptr
)) == NULL
)
1082 * Add the last message string to the array as needed...
1087 if (m
->str
&& m
->str
[0])
1094 * Translation is empty, don't add it... (STR #4033)
1105 * Close the message catalog file and return the new array...
1110 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
1111 cupsArrayCount(a
)));
1118 * '_cupsMessageLookup()' - Lookup a message string.
1121 const char * /* O - Localized message */
1122 _cupsMessageLookup(cups_array_t
*a
, /* I - Message array */
1123 const char *m
) /* I - Message */
1125 _cups_message_t key
, /* Search key */
1126 *match
; /* Matching message */
1130 * Lookup the message string; if it doesn't exist in the catalog,
1131 * then return the message that was passed to us...
1135 match
= (_cups_message_t
*)cupsArrayFind(a
, &key
);
1137 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1138 if (!match
&& cupsArrayUserData(a
))
1141 * Try looking the string up in the cups.strings dictionary...
1144 CFDictionaryRef dict
; /* cups.strings dictionary */
1145 CFStringRef cfm
, /* Message as a CF string */
1146 cfstr
; /* Localized text as a CF string */
1148 dict
= (CFDictionaryRef
)cupsArrayUserData(a
);
1149 cfm
= CFStringCreateWithCString(kCFAllocatorDefault
, m
,
1150 kCFStringEncodingUTF8
);
1151 match
= calloc(1, sizeof(_cups_message_t
));
1152 match
->id
= strdup(m
);
1153 cfstr
= cfm
? CFDictionaryGetValue(dict
, cfm
) : NULL
;
1157 char buffer
[1024]; /* Message buffer */
1159 CFStringGetCString(cfstr
, buffer
, sizeof(buffer
), kCFStringEncodingUTF8
);
1160 match
->str
= strdup(buffer
);
1162 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...",
1167 match
->str
= strdup(m
);
1169 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m
));
1172 cupsArrayAdd(a
, match
);
1177 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1179 if (match
&& match
->str
)
1180 return (match
->str
);
1187 * '_cupsMessageNew()' - Make a new message catalog array.
1190 cups_array_t
* /* O - Array */
1191 _cupsMessageNew(void *context
) /* I - User data */
1193 return (cupsArrayNew3((cups_array_func_t
)cups_message_compare
, context
,
1194 (cups_ahash_func_t
)NULL
, 0,
1195 (cups_acopy_func_t
)NULL
,
1196 (cups_afree_func_t
)cups_message_free
));
1202 * 'appleLangDefault()' - Get the default locale string.
1205 static const char * /* O - Locale string */
1206 appleLangDefault(void)
1208 CFBundleRef bundle
; /* Main bundle (if any) */
1209 CFArrayRef bundleList
; /* List of localizations in bundle */
1210 CFPropertyListRef localizationList
= NULL
;
1211 /* List of localization data */
1212 CFStringRef languageName
; /* Current name */
1213 char *lang
; /* LANG environment variable */
1214 _cups_globals_t
*cg
= _cupsGlobals();
1215 /* Pointer to library globals */
1218 DEBUG_puts("2appleLangDefault()");
1221 * Only do the lookup and translation the first time.
1224 if (!cg
->language
[0])
1226 if (getenv("SOFTWARE") != NULL
&& (lang
= getenv("LANG")) != NULL
)
1228 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang
));
1229 strlcpy(cg
->language
, lang
, sizeof(cg
->language
));
1230 return (cg
->language
);
1232 else if ((bundle
= CFBundleGetMainBundle()) != NULL
&&
1233 (bundleList
= CFBundleCopyBundleLocalizations(bundle
)) != NULL
)
1235 CFURLRef resources
= CFBundleCopyResourcesDirectoryURL(bundle
);
1237 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
1241 CFStringRef cfpath
= CFURLCopyPath(resources
);
1247 * See if we have an Info.plist file in the bundle...
1250 CFStringGetCString(cfpath
, path
, sizeof(path
), kCFStringEncodingUTF8
);
1251 DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path
));
1252 strlcat(path
, "Contents/Info.plist", sizeof(path
));
1254 if (!access(path
, R_OK
))
1255 localizationList
= CFBundleCopyPreferredLocalizationsFromArray(bundleList
);
1257 DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL...");
1262 CFRelease(resources
);
1265 DEBUG_puts("3appleLangDefault: No resource URL.");
1267 CFRelease(bundleList
);
1270 if (!localizationList
)
1272 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
1275 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1276 kCFPreferencesCurrentApplication
);
1279 if (localizationList
)
1282 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID())
1283 DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.",
1284 (int)CFArrayGetCount(localizationList
)));
1286 DEBUG_puts("3appleLangDefault: Got localizationList but not an array.");
1289 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID() &&
1290 CFArrayGetCount(localizationList
) > 0)
1292 languageName
= CFArrayGetValueAtIndex(localizationList
, 0);
1295 CFGetTypeID(languageName
) == CFStringGetTypeID())
1297 if (_cupsAppleLocale(languageName
, cg
->language
, sizeof(cg
->language
)))
1298 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
1301 DEBUG_puts("3appleLangDefault: Unable to get locale.");
1305 CFRelease(localizationList
);
1309 * If we didn't find the language, default to en_US...
1312 if (!cg
->language
[0])
1314 DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
1315 strlcpy(cg
->language
, "en_US.UTF-8", sizeof(cg
->language
));
1319 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg
->language
));
1322 * Return the cached locale...
1325 return (cg
->language
);
1329 # ifdef CUPS_BUNDLEDIR
1331 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1334 static cups_array_t
* /* O - Message catalog */
1335 appleMessageLoad(const char *locale
) /* I - Locale ID */
1337 char filename
[1024], /* Path to cups.strings file */
1338 applelang
[256], /* Apple language ID */
1339 baselang
[3]; /* Base language */
1340 CFURLRef url
; /* URL to cups.strings file */
1341 CFReadStreamRef stream
= NULL
; /* File stream */
1342 CFPropertyListRef plist
= NULL
; /* Localization file */
1344 CFErrorRef error
= NULL
; /* Error when opening file */
1348 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale
));
1351 * Load the cups.strings file...
1354 snprintf(filename
, sizeof(filename
),
1355 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings",
1356 _cupsAppleLanguage(locale
, applelang
, sizeof(applelang
)));
1358 if (access(filename
, 0))
1361 * <rdar://problem/22086642>
1363 * Try with original locale string...
1366 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1369 if (access(filename
, 0))
1372 * <rdar://problem/25292403>
1374 * Try with just the language code...
1377 strlcpy(baselang
, locale
, sizeof(baselang
));
1378 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", baselang
);
1381 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename
));
1383 if (access(filename
, 0))
1386 * Try alternate lproj directory names...
1389 if (!strncmp(locale
, "en", 2))
1391 else if (!strncmp(locale
, "nb", 2))
1393 else if (!strncmp(locale
, "nl", 2))
1395 else if (!strncmp(locale
, "fr", 2))
1397 else if (!strncmp(locale
, "de", 2))
1399 else if (!strncmp(locale
, "it", 2))
1401 else if (!strncmp(locale
, "ja", 2))
1402 locale
= "Japanese";
1403 else if (!strncmp(locale
, "es", 2))
1405 else if (!strcmp(locale
, "zh_HK") || !strncmp(locale
, "zh-Hant", 7))
1408 * <rdar://problem/22130168>
1409 * <rdar://problem/27245567>
1411 * Try zh_TW first, then zh... Sigh...
1414 if (!access(CUPS_BUNDLEDIR
"/Resources/zh_TW.lproj/cups.strings", 0))
1419 else if (strstr(locale
, "_") != NULL
|| strstr(locale
, "-") != NULL
)
1422 * Drop country code, just try language...
1425 strlcpy(baselang
, locale
, sizeof(baselang
));
1429 snprintf(filename
, sizeof(filename
),
1430 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1431 DEBUG_printf(("1appleMessageLoad: alternate filename=\"%s\"", filename
));
1434 url
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
,
1436 (CFIndex
)strlen(filename
), false);
1439 stream
= CFReadStreamCreateWithFile(kCFAllocatorDefault
, url
);
1443 * Read the property list containing the localization data.
1445 * NOTE: This code currently generates a clang "potential leak"
1446 * warning, but the object is released in _cupsMessageFree().
1449 CFReadStreamOpen(stream
);
1452 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1453 kCFPropertyListImmutable
, NULL
,
1457 CFStringRef msg
= CFErrorCopyDescription(error
);
1460 CFStringGetCString(msg
, filename
, sizeof(filename
),
1461 kCFStringEncodingUTF8
);
1462 DEBUG_printf(("1appleMessageLoad: %s", filename
));
1469 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1470 kCFPropertyListImmutable
, NULL
,
1474 if (plist
&& CFGetTypeID(plist
) != CFDictionaryGetTypeID())
1486 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url
, stream
,
1490 * Create and return an empty array to act as a cache for messages, passing the
1491 * plist as the user data.
1494 return (_cupsMessageNew((void *)plist
));
1496 # endif /* CUPS_BUNDLEDIR */
1497 #endif /* __APPLE__ */
1501 * 'cups_cache_lookup()' - Lookup a language in the cache...
1504 static cups_lang_t
* /* O - Language data or NULL */
1506 const char *name
, /* I - Name of locale */
1507 cups_encoding_t encoding
) /* I - Encoding of locale */
1509 cups_lang_t
*lang
; /* Current language */
1512 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name
,
1513 encoding
, encoding
== CUPS_AUTO_ENCODING
? "auto" :
1514 lang_encodings
[encoding
]));
1517 * Loop through the cache and return a match if found...
1520 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
1522 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1523 "encoding=%d(%s)", (void *)lang
, lang
->language
, lang
->encoding
,
1524 lang_encodings
[lang
->encoding
]));
1526 if (!strcmp(lang
->language
, name
) &&
1527 (encoding
== CUPS_AUTO_ENCODING
|| encoding
== lang
->encoding
))
1531 DEBUG_puts("8cups_cache_lookup: returning match!");
1537 DEBUG_puts("8cups_cache_lookup: returning NULL!");
1544 * 'cups_message_compare()' - Compare two messages.
1547 static int /* O - Result of comparison */
1548 cups_message_compare(
1549 _cups_message_t
*m1
, /* I - First message */
1550 _cups_message_t
*m2
) /* I - Second message */
1552 return (strcmp(m1
->id
, m2
->id
));
1557 * 'cups_message_free()' - Free a message.
1561 cups_message_free(_cups_message_t
*m
) /* I - Message */
1574 * 'cups_message_load()' - Load the message catalog for a language.
1578 cups_message_load(cups_lang_t
*lang
) /* I - Language */
1580 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1581 lang
->strings
= appleMessageLoad(lang
->language
);
1584 char filename
[1024]; /* Filename for language locale file */
1585 _cups_globals_t
*cg
= _cupsGlobals();
1586 /* Pointer to library globals */
1589 snprintf(filename
, sizeof(filename
), "%s/%s/cups_%s.po", cg
->localedir
,
1590 lang
->language
, lang
->language
);
1592 if (strchr(lang
->language
, '_') && access(filename
, 0))
1595 * Country localization not available, look for generic localization...
1598 snprintf(filename
, sizeof(filename
), "%s/%.2s/cups_%.2s.po", cg
->localedir
,
1599 lang
->language
, lang
->language
);
1601 if (access(filename
, 0))
1604 * No generic localization, so use POSIX...
1607 DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename
,
1610 snprintf(filename
, sizeof(filename
), "%s/C/cups_C.po", cg
->localedir
);
1615 * Read the strings from the file...
1618 lang
->strings
= _cupsMessageLoad(filename
, 1);
1619 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1624 * 'cups_unquote()' - Unquote characters in strings...
1628 cups_unquote(char *d
, /* O - Unquoted string */
1629 const char *s
) /* I - Original string */
1642 *d
= *d
* 8 + *s
- '0';