2 * I18N/language support for CUPS.
4 * Copyright 2007-2017 by Apple Inc.
5 * Copyright 1997-2007 by Easy Software Products.
7 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
11 * Include necessary headers...
14 #include "cups-private.h"
15 #ifdef HAVE_LANGINFO_H
16 # include <langinfo.h>
17 #endif /* HAVE_LANGINFO_H */
23 #ifdef HAVE_COREFOUNDATION_H
24 # include <CoreFoundation/CoreFoundation.h>
25 #endif /* HAVE_COREFOUNDATION_H */
32 static _cups_mutex_t lang_mutex
= _CUPS_MUTEX_INITIALIZER
;
33 /* Mutex to control access to cache */
34 static cups_lang_t
*lang_cache
= NULL
;
35 /* Language string cache */
36 static const char * const lang_encodings
[] =
37 { /* Encoding strings */
38 "us-ascii", "iso-8859-1",
39 "iso-8859-2", "iso-8859-3",
40 "iso-8859-4", "iso-8859-5",
41 "iso-8859-6", "iso-8859-7",
42 "iso-8859-8", "iso-8859-9",
43 "iso-8859-10", "utf-8",
44 "iso-8859-13", "iso-8859-14",
45 "iso-8859-15", "cp874",
51 "koi8-u", "iso-8859-11",
100 "unknown", "unknown",
101 "unknown", "unknown",
110 const char * const language
; /* Language ID */
111 const char * const locale
; /* Locale ID */
112 } _apple_language_locale_t
;
114 static const _apple_language_locale_t apple_language_locale
[] =
115 { /* Language to locale ID LUT */
119 { "zh-Hans", "zh_CN" },
120 { "zh_HANS", "zh_CN" },
121 { "zh-Hant", "zh_TW" },
122 { "zh_HANT", "zh_TW" },
123 { "zh-Hant_CN", "zh_TW" }
125 #endif /* __APPLE__ */
134 static const char *appleLangDefault(void);
135 # ifdef CUPS_BUNDLEDIR
136 # ifndef CF_RETURNS_RETAINED
137 # if __has_feature(attribute_cf_returns_retained)
138 # define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
140 # define CF_RETURNS_RETAINED
141 # endif /* __has_feature(attribute_cf_returns_retained) */
142 # endif /* !CF_RETURNED_RETAINED */
143 static cups_array_t
*appleMessageLoad(const char *locale
)
145 # endif /* CUPS_BUNDLEDIR */
146 #endif /* __APPLE__ */
147 static cups_lang_t
*cups_cache_lookup(const char *name
,
148 cups_encoding_t encoding
);
149 static int cups_message_compare(_cups_message_t
*m1
,
150 _cups_message_t
*m2
);
151 static void cups_message_free(_cups_message_t
*m
);
152 static void cups_message_load(cups_lang_t
*lang
);
153 static void cups_unquote(char *d
, const char *s
);
158 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
162 const char * /* O - Language ID */
163 _cupsAppleLanguage(const char *locale
, /* I - Locale ID */
164 char *language
,/* I - Language ID buffer */
165 size_t langsize
) /* I - Size of language ID buffer */
167 int i
; /* Looping var */
168 CFStringRef localeid
, /* CF locale identifier */
169 langid
; /* CF language identifier */
173 * Copy the locale name and convert, as needed, to the Apple-specific
174 * locale identifier...
177 switch (strlen(locale
))
184 strlcpy(language
, "en", langsize
);
188 strlcpy(language
, locale
, langsize
);
192 strlcpy(language
, locale
, langsize
);
194 if (language
[2] == '-')
197 * Convert ll-cc to ll_CC...
201 language
[3] = (char)toupper(language
[3] & 255);
202 language
[4] = (char)toupper(language
[4] & 255);
208 i
< (int)(sizeof(apple_language_locale
) /
209 sizeof(apple_language_locale
[0]));
211 if (!strcmp(locale
, apple_language_locale
[i
].locale
))
213 strlcpy(language
, apple_language_locale
[i
].language
, sizeof(language
));
218 * Attempt to map the locale ID to a language ID...
221 if ((localeid
= CFStringCreateWithCString(kCFAllocatorDefault
, language
,
222 kCFStringEncodingASCII
)) != NULL
)
224 if ((langid
= CFLocaleCreateCanonicalLanguageIdentifierFromString(
225 kCFAllocatorDefault
, localeid
)) != NULL
)
227 CFStringGetCString(langid
, language
, (CFIndex
)langsize
, kCFStringEncodingASCII
);
235 * Return what we got...
243 * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID.
246 const char * /* O - Locale */
247 _cupsAppleLocale(CFStringRef languageName
, /* I - Apple language ID */
248 char *locale
, /* I - Buffer for locale */
249 size_t localesize
) /* I - Size of buffer */
251 int i
; /* Looping var */
252 CFStringRef localeName
; /* Locale as a CF string */
254 char temp
[1024]; /* Temporary string */
257 if (!CFStringGetCString(languageName
, temp
, (CFIndex
)sizeof(temp
), kCFStringEncodingASCII
))
260 DEBUG_printf(("_cupsAppleLocale(languageName=%p(%s), locale=%p, localsize=%d)", (void *)languageName
, temp
, (void *)locale
, (int)localesize
));
263 localeName
= CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault
, languageName
);
268 * Copy the locale name and tweak as needed...
271 if (!CFStringGetCString(localeName
, locale
, (CFIndex
)localesize
, kCFStringEncodingASCII
))
274 DEBUG_printf(("_cupsAppleLocale: locale=\"%s\"", locale
));
276 CFRelease(localeName
);
279 * Map new language identifiers to locales...
283 i
< (int)(sizeof(apple_language_locale
) /
284 sizeof(apple_language_locale
[0]));
287 size_t len
= strlen(apple_language_locale
[i
].language
);
289 if (!strcmp(locale
, apple_language_locale
[i
].language
) ||
290 (!strncmp(locale
, apple_language_locale
[i
].language
, len
) && (locale
[len
] == '_' || locale
[len
] == '-')))
292 DEBUG_printf(("_cupsAppleLocale: Updating locale to \"%s\".", apple_language_locale
[i
].locale
));
293 strlcpy(locale
, apple_language_locale
[i
].locale
, localesize
);
301 * Just try the Apple language name...
304 if (!CFStringGetCString(languageName
, locale
, (CFIndex
)localesize
, kCFStringEncodingASCII
))
310 DEBUG_puts("_cupsAppleLocale: Returning NULL.");
315 * Convert language subtag into region subtag...
318 if (locale
[2] == '-')
320 else if (locale
[3] == '-')
323 if (!strchr(locale
, '.'))
324 strlcat(locale
, ".UTF-8", localesize
);
326 DEBUG_printf(("_cupsAppleLocale: Returning \"%s\".", locale
));
330 #endif /* __APPLE__ */
334 * '_cupsEncodingName()' - Return the character encoding name string
335 * for the given encoding enumeration.
338 const char * /* O - Character encoding */
340 cups_encoding_t encoding
) /* I - Encoding value */
342 if (encoding
< CUPS_US_ASCII
||
343 encoding
>= (cups_encoding_t
)(sizeof(lang_encodings
) / sizeof(lang_encodings
[0])))
345 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
346 encoding
, lang_encodings
[0]));
347 return (lang_encodings
[0]);
351 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
352 encoding
, lang_encodings
[encoding
]));
353 return (lang_encodings
[encoding
]);
359 * 'cupsLangDefault()' - Return the default language.
362 cups_lang_t
* /* O - Language data */
363 cupsLangDefault(void)
365 return (cupsLangGet(NULL
));
370 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
371 * for the given language.
374 const char * /* O - Character encoding */
375 cupsLangEncoding(cups_lang_t
*lang
) /* I - Language data */
378 return ((char*)lang_encodings
[0]);
380 return ((char*)lang_encodings
[lang
->encoding
]);
385 * 'cupsLangFlush()' - Flush all language data out of the cache.
391 cups_lang_t
*lang
, /* Current language */
392 *next
; /* Next language */
396 * Free all languages in the cache...
399 _cupsMutexLock(&lang_mutex
);
401 for (lang
= lang_cache
; lang
!= NULL
; lang
= next
)
404 * Free all messages...
407 _cupsMessageFree(lang
->strings
);
410 * Then free the language structure itself...
419 _cupsMutexUnlock(&lang_mutex
);
424 * 'cupsLangFree()' - Free language data.
426 * This does not actually free anything; use @link cupsLangFlush@ for that.
430 cupsLangFree(cups_lang_t
*lang
) /* I - Language to free */
432 _cupsMutexLock(&lang_mutex
);
434 if (lang
!= NULL
&& lang
->used
> 0)
437 _cupsMutexUnlock(&lang_mutex
);
442 * 'cupsLangGet()' - Get a language.
445 cups_lang_t
* /* O - Language data */
446 cupsLangGet(const char *language
) /* I - Language or locale */
448 int i
; /* Looping var */
450 char locale
[255]; /* Copy of locale name */
451 #endif /* !__APPLE__ */
452 char langname
[16], /* Requested language name */
453 country
[16], /* Country code */
454 charset
[16], /* Character set */
455 *csptr
, /* Pointer to CODESET string */
456 *ptr
, /* Pointer into language/charset */
457 real
[48]; /* Real language name */
458 cups_encoding_t encoding
; /* Encoding to use */
459 cups_lang_t
*lang
; /* Current language... */
460 static const char * const locale_encodings
[] =
461 { /* Locale charset names */
462 "ASCII", "ISO88591", "ISO88592", "ISO88593",
463 "ISO88594", "ISO88595", "ISO88596", "ISO88597",
464 "ISO88598", "ISO88599", "ISO885910", "UTF8",
465 "ISO885913", "ISO885914", "ISO885915", "CP874",
466 "CP1250", "CP1251", "CP1252", "CP1253",
467 "CP1254", "CP1255", "CP1256", "CP1257",
468 "CP1258", "KOI8R", "KOI8U", "ISO885911",
469 "ISO885916", "MACROMAN", "", "",
480 "CP932", "CP936", "CP949", "CP950",
481 "CP1361", "", "", "",
498 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
503 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language
));
507 * Set the character set to UTF-8...
510 strlcpy(charset
, "UTF8", sizeof(charset
));
513 * Apple's setlocale doesn't give us the user's localization
514 * preference so we have to look it up this way...
519 if (!getenv("SOFTWARE") || (language
= getenv("LANG")) == NULL
)
520 language
= appleLangDefault();
522 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language
));
527 * Set the charset to "unknown"...
533 * Use setlocale() to determine the currently set locale, and then
534 * fallback to environment variables to avoid setting the locale,
535 * since setlocale() is not thread-safe!
541 * First see if the locale has been set; if it is still "C" or
542 * "POSIX", use the environment to get the default...
546 ptr
= setlocale(LC_MESSAGES
, NULL
);
548 ptr
= setlocale(LC_ALL
, NULL
);
549 # endif /* LC_MESSAGES */
551 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr
));
553 if (!ptr
|| !strcmp(ptr
, "C") || !strcmp(ptr
, "POSIX"))
556 * Get the character set from the LC_CTYPE locale setting...
559 if ((ptr
= getenv("LC_CTYPE")) == NULL
)
560 if ((ptr
= getenv("LC_ALL")) == NULL
)
561 if ((ptr
= getenv("LANG")) == NULL
)
564 if ((csptr
= strchr(ptr
, '.')) != NULL
)
567 * Extract the character set from the environment...
570 for (ptr
= charset
, csptr
++; *csptr
; csptr
++)
571 if (ptr
< (charset
+ sizeof(charset
) - 1) && _cups_isalnum(*csptr
))
578 * Get the locale for messages from the LC_MESSAGES locale setting...
581 if ((ptr
= getenv("LC_MESSAGES")) == NULL
)
582 if ((ptr
= getenv("LC_ALL")) == NULL
)
583 if ((ptr
= getenv("LANG")) == NULL
)
589 strlcpy(locale
, ptr
, sizeof(locale
));
593 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
596 if (!strncmp(locale
, "nb", 2))
599 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language
));
602 #endif /* __APPLE__ */
605 * If "language" is NULL at this point, then chances are we are using
606 * a language that is not installed for the base OS.
612 * Switch to the POSIX ("C") locale...
620 * On systems that support the nl_langinfo(CODESET) call, use
621 * this value as the character set...
624 if (!charset
[0] && (csptr
= nl_langinfo(CODESET
)) != NULL
)
627 * Copy all of the letters and numbers in the CODESET string...
630 for (ptr
= charset
; *csptr
; csptr
++)
631 if (_cups_isalnum(*csptr
) && ptr
< (charset
+ sizeof(charset
) - 1))
636 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
637 "nl_langinfo(CODESET)...", charset
));
642 * If we don't have a character set by now, default to UTF-8...
646 strlcpy(charset
, "UTF8", sizeof(charset
));
649 * Parse the language string passed in to a locale string. "C" is the
650 * standard POSIX locale and is copied unchanged. Otherwise the
651 * language string is converted from ll-cc[.charset] (language-country)
652 * to ll_CC[.CHARSET] to match the file naming convention used by all
653 * POSIX-compliant operating systems. Invalid language names are mapped
654 * to the POSIX locale.
659 if (language
== NULL
|| !language
[0] ||
660 !strcmp(language
, "POSIX"))
661 strlcpy(langname
, "C", sizeof(langname
));
665 * Copy the parts of the locale string over safely...
668 for (ptr
= langname
; *language
; language
++)
669 if (*language
== '_' || *language
== '-' || *language
== '.')
671 else if (ptr
< (langname
+ sizeof(langname
) - 1))
672 *ptr
++ = (char)tolower(*language
& 255);
676 if (*language
== '_' || *language
== '-')
679 * Copy the country code...
682 for (language
++, ptr
= country
; *language
; language
++)
683 if (*language
== '.')
685 else if (ptr
< (country
+ sizeof(country
) - 1))
686 *ptr
++ = (char)toupper(*language
& 255);
691 * Map Chinese region codes to legacy country codes.
694 if (!strcmp(language
, "zh") && !strcmp(country
, "HANS"))
695 strlcpy(country
, "CN", sizeof(country
));
696 if (!strcmp(language
, "zh") && !strcmp(country
, "HANT"))
697 strlcpy(country
, "TW", sizeof(country
));
700 if (*language
== '.' && !charset
[0])
703 * Copy the encoding...
706 for (language
++, ptr
= charset
; *language
; language
++)
707 if (_cups_isalnum(*language
) && ptr
< (charset
+ sizeof(charset
) - 1))
708 *ptr
++ = (char)toupper(*language
& 255);
714 * Force a POSIX locale for an invalid language name...
717 if (strlen(langname
) != 2 && strlen(langname
) != 3)
719 strlcpy(langname
, "C", sizeof(langname
));
725 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
726 langname
, country
, charset
));
729 * Figure out the desired encoding...
732 encoding
= CUPS_AUTO_ENCODING
;
737 i
< (int)(sizeof(locale_encodings
) / sizeof(locale_encodings
[0]));
739 if (!_cups_strcasecmp(charset
, locale_encodings
[i
]))
741 encoding
= (cups_encoding_t
)i
;
745 if (encoding
== CUPS_AUTO_ENCODING
)
748 * Map alternate names for various character sets...
751 if (!_cups_strcasecmp(charset
, "iso-2022-jp") ||
752 !_cups_strcasecmp(charset
, "sjis"))
753 encoding
= CUPS_WINDOWS_932
;
754 else if (!_cups_strcasecmp(charset
, "iso-2022-cn"))
755 encoding
= CUPS_WINDOWS_936
;
756 else if (!_cups_strcasecmp(charset
, "iso-2022-kr"))
757 encoding
= CUPS_WINDOWS_949
;
758 else if (!_cups_strcasecmp(charset
, "big5"))
759 encoding
= CUPS_WINDOWS_950
;
763 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding
,
764 encoding
== CUPS_AUTO_ENCODING
? "auto" :
765 lang_encodings
[encoding
]));
768 * See if we already have this language/country loaded...
772 snprintf(real
, sizeof(real
), "%s_%s", langname
, country
);
774 strlcpy(real
, langname
, sizeof(real
));
776 _cupsMutexLock(&lang_mutex
);
778 if ((lang
= cups_cache_lookup(real
, encoding
)) != NULL
)
780 _cupsMutexUnlock(&lang_mutex
);
782 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real
));
788 * See if there is a free language available; if so, use that
792 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
799 * Allocate memory for the language and add it to the cache.
802 if ((lang
= calloc(sizeof(cups_lang_t
), 1)) == NULL
)
804 _cupsMutexUnlock(&lang_mutex
);
809 lang
->next
= lang_cache
;
815 * Free all old strings as needed...
818 _cupsMessageFree(lang
->strings
);
819 lang
->strings
= NULL
;
823 * Then assign the language and encoding fields...
827 strlcpy(lang
->language
, real
, sizeof(lang
->language
));
829 if (encoding
!= CUPS_AUTO_ENCODING
)
830 lang
->encoding
= encoding
;
832 lang
->encoding
= CUPS_UTF8
;
838 _cupsMutexUnlock(&lang_mutex
);
845 * '_cupsLangString()' - Get a message string.
847 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
848 * convert the string to the language encoding.
851 const char * /* O - Localized message */
852 _cupsLangString(cups_lang_t
*lang
, /* I - Language */
853 const char *message
) /* I - Message */
855 const char *s
; /* Localized message */
858 DEBUG_printf(("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang
, message
));
861 * Range check input...
864 if (!lang
|| !message
|| !*message
)
867 _cupsMutexLock(&lang_mutex
);
870 * Load the message catalog if needed...
874 cups_message_load(lang
);
876 s
= _cupsMessageLookup(lang
->strings
, message
);
878 _cupsMutexUnlock(&lang_mutex
);
885 * '_cupsMessageFree()' - Free a messages array.
889 _cupsMessageFree(cups_array_t
*a
) /* I - Message array */
891 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
893 * Release the cups.strings dictionary as needed...
896 if (cupsArrayUserData(a
))
897 CFRelease((CFDictionaryRef
)cupsArrayUserData(a
));
898 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
909 * '_cupsMessageLoad()' - Load a .po file into a messages array.
912 cups_array_t
* /* O - New message array */
913 _cupsMessageLoad(const char *filename
, /* I - Message catalog to load */
914 int unquote
) /* I - Unescape \foo in strings? */
916 cups_file_t
*fp
; /* Message file */
917 cups_array_t
*a
; /* Message array */
918 _cups_message_t
*m
; /* Current message */
919 char s
[4096], /* String buffer */
920 *ptr
, /* Pointer into buffer */
921 *temp
; /* New string */
922 size_t length
, /* Length of combined strings */
923 ptrlen
; /* Length of string */
926 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename
));
929 * Create an array to hold the messages...
932 if ((a
= _cupsMessageNew(NULL
)) == NULL
)
934 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
939 * Open the message catalog file...
942 if ((fp
= cupsFileOpen(filename
, "r")) == NULL
)
944 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
950 * Read messages from the catalog file until EOF...
952 * The format is the GNU gettext .po format, which is fairly simple:
955 * msgstr "localized text"
957 * The ID and localized text can span multiple lines using the form:
962 * "localized text spanning "
968 while (cupsFileGets(fp
, s
, sizeof(s
)) != NULL
)
971 * Skip blank and comment lines...
974 if (s
[0] == '#' || !s
[0])
978 * Strip the trailing quote...
981 if ((ptr
= strrchr(s
, '\"')) == NULL
)
987 * Find start of value...
990 if ((ptr
= strchr(s
, '\"')) == NULL
)
996 * Unquote the text...
1000 cups_unquote(ptr
, ptr
);
1003 * Create or add to a message...
1006 if (!strncmp(s
, "msgid", 5))
1009 * Add previous message as needed...
1014 if (m
->str
&& m
->str
[0])
1021 * Translation is empty, don't add it... (STR #4033)
1032 * Create a new message with the given msgid string...
1035 if ((m
= (_cups_message_t
*)calloc(1, sizeof(_cups_message_t
))) == NULL
)
1041 if ((m
->id
= strdup(ptr
)) == NULL
)
1048 else if (s
[0] == '\"' && m
)
1051 * Append to current string...
1054 length
= strlen(m
->str
? m
->str
: m
->id
);
1055 ptrlen
= strlen(ptr
);
1057 if ((temp
= realloc(m
->str
? m
->str
: m
->id
, length
+ ptrlen
+ 1)) == NULL
)
1071 * Copy the new portion to the end of the msgstr string - safe
1072 * to use memcpy because the buffer is allocated to the correct
1078 memcpy(m
->str
+ length
, ptr
, ptrlen
+ 1);
1083 * Copy the new portion to the end of the msgid string - safe
1084 * to use memcpy because the buffer is allocated to the correct
1090 memcpy(m
->id
+ length
, ptr
, ptrlen
+ 1);
1093 else if (!strncmp(s
, "msgstr", 6) && m
)
1099 if ((m
->str
= strdup(ptr
)) == NULL
)
1111 * Add the last message string to the array as needed...
1116 if (m
->str
&& m
->str
[0])
1123 * Translation is empty, don't add it... (STR #4033)
1134 * Close the message catalog file and return the new array...
1139 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
1140 cupsArrayCount(a
)));
1147 * '_cupsMessageLookup()' - Lookup a message string.
1150 const char * /* O - Localized message */
1151 _cupsMessageLookup(cups_array_t
*a
, /* I - Message array */
1152 const char *m
) /* I - Message */
1154 _cups_message_t key
, /* Search key */
1155 *match
; /* Matching message */
1158 DEBUG_printf(("_cupsMessageLookup(a=%p, m=\"%s\")", (void *)a
, m
));
1161 * Lookup the message string; if it doesn't exist in the catalog,
1162 * then return the message that was passed to us...
1166 match
= (_cups_message_t
*)cupsArrayFind(a
, &key
);
1168 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1169 if (!match
&& cupsArrayUserData(a
))
1172 * Try looking the string up in the cups.strings dictionary...
1175 CFDictionaryRef dict
; /* cups.strings dictionary */
1176 CFStringRef cfm
, /* Message as a CF string */
1177 cfstr
; /* Localized text as a CF string */
1179 dict
= (CFDictionaryRef
)cupsArrayUserData(a
);
1180 cfm
= CFStringCreateWithCString(kCFAllocatorDefault
, m
,
1181 kCFStringEncodingUTF8
);
1182 match
= calloc(1, sizeof(_cups_message_t
));
1183 match
->id
= strdup(m
);
1184 cfstr
= cfm
? CFDictionaryGetValue(dict
, cfm
) : NULL
;
1188 char buffer
[1024]; /* Message buffer */
1190 CFStringGetCString(cfstr
, buffer
, sizeof(buffer
), kCFStringEncodingUTF8
);
1191 match
->str
= strdup(buffer
);
1193 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...",
1198 match
->str
= strdup(m
);
1200 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m
));
1203 cupsArrayAdd(a
, match
);
1208 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1210 if (match
&& match
->str
)
1211 return (match
->str
);
1218 * '_cupsMessageNew()' - Make a new message catalog array.
1221 cups_array_t
* /* O - Array */
1222 _cupsMessageNew(void *context
) /* I - User data */
1224 return (cupsArrayNew3((cups_array_func_t
)cups_message_compare
, context
,
1225 (cups_ahash_func_t
)NULL
, 0,
1226 (cups_acopy_func_t
)NULL
,
1227 (cups_afree_func_t
)cups_message_free
));
1233 * 'appleLangDefault()' - Get the default locale string.
1236 static const char * /* O - Locale string */
1237 appleLangDefault(void)
1239 CFBundleRef bundle
; /* Main bundle (if any) */
1240 CFArrayRef bundleList
; /* List of localizations in bundle */
1241 CFPropertyListRef localizationList
= NULL
;
1242 /* List of localization data */
1243 CFStringRef languageName
; /* Current name */
1244 char *lang
; /* LANG environment variable */
1245 _cups_globals_t
*cg
= _cupsGlobals();
1246 /* Pointer to library globals */
1249 DEBUG_puts("2appleLangDefault()");
1252 * Only do the lookup and translation the first time.
1255 if (!cg
->language
[0])
1257 if (getenv("SOFTWARE") != NULL
&& (lang
= getenv("LANG")) != NULL
)
1259 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang
));
1260 strlcpy(cg
->language
, lang
, sizeof(cg
->language
));
1261 return (cg
->language
);
1263 else if ((bundle
= CFBundleGetMainBundle()) != NULL
&&
1264 (bundleList
= CFBundleCopyBundleLocalizations(bundle
)) != NULL
)
1266 CFURLRef resources
= CFBundleCopyResourcesDirectoryURL(bundle
);
1268 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
1272 CFStringRef cfpath
= CFURLCopyPath(resources
);
1278 * See if we have an Info.plist file in the bundle...
1281 CFStringGetCString(cfpath
, path
, sizeof(path
), kCFStringEncodingUTF8
);
1282 DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path
));
1283 strlcat(path
, "Contents/Info.plist", sizeof(path
));
1285 if (!access(path
, R_OK
))
1286 localizationList
= CFBundleCopyPreferredLocalizationsFromArray(bundleList
);
1288 DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL...");
1293 CFRelease(resources
);
1296 DEBUG_puts("3appleLangDefault: No resource URL.");
1298 CFRelease(bundleList
);
1301 if (!localizationList
)
1303 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
1306 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1307 kCFPreferencesCurrentApplication
);
1310 if (localizationList
)
1313 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID())
1314 DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.",
1315 (int)CFArrayGetCount(localizationList
)));
1317 DEBUG_puts("3appleLangDefault: Got localizationList but not an array.");
1320 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID() &&
1321 CFArrayGetCount(localizationList
) > 0)
1323 languageName
= CFArrayGetValueAtIndex(localizationList
, 0);
1326 CFGetTypeID(languageName
) == CFStringGetTypeID())
1328 if (_cupsAppleLocale(languageName
, cg
->language
, sizeof(cg
->language
)))
1329 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
1332 DEBUG_puts("3appleLangDefault: Unable to get locale.");
1336 CFRelease(localizationList
);
1340 * If we didn't find the language, default to en_US...
1343 if (!cg
->language
[0])
1345 DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
1346 strlcpy(cg
->language
, "en_US.UTF-8", sizeof(cg
->language
));
1350 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg
->language
));
1353 * Return the cached locale...
1356 return (cg
->language
);
1360 # ifdef CUPS_BUNDLEDIR
1362 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1365 static cups_array_t
* /* O - Message catalog */
1366 appleMessageLoad(const char *locale
) /* I - Locale ID */
1368 char filename
[1024], /* Path to cups.strings file */
1369 applelang
[256], /* Apple language ID */
1370 baselang
[4]; /* Base language */
1371 CFURLRef url
; /* URL to cups.strings file */
1372 CFReadStreamRef stream
= NULL
; /* File stream */
1373 CFPropertyListRef plist
= NULL
; /* Localization file */
1375 const char *cups_strings
= getenv("CUPS_STRINGS");
1376 /* Test strings file */
1377 CFErrorRef error
= NULL
; /* Error when opening file */
1381 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale
));
1384 * Load the cups.strings file...
1390 DEBUG_puts("1appleMessageLoad: Using debug CUPS_STRINGS file.");
1391 strlcpy(filename
, cups_strings
, sizeof(filename
));
1396 snprintf(filename
, sizeof(filename
),
1397 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings",
1398 _cupsAppleLanguage(locale
, applelang
, sizeof(applelang
)));
1400 if (access(filename
, 0))
1403 * <rdar://problem/22086642>
1405 * Try with original locale string...
1408 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1409 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1412 if (access(filename
, 0))
1415 * <rdar://problem/25292403>
1417 * Try with just the language code...
1420 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1422 strlcpy(baselang
, locale
, sizeof(baselang
));
1423 if (baselang
[3] == '-' || baselang
[3] == '_')
1426 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", baselang
);
1429 if (access(filename
, 0))
1432 * Try alternate lproj directory names...
1435 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1437 if (!strncmp(locale
, "en", 2))
1439 else if (!strncmp(locale
, "nb", 2))
1441 else if (!strncmp(locale
, "nl", 2))
1443 else if (!strncmp(locale
, "fr", 2))
1445 else if (!strncmp(locale
, "de", 2))
1447 else if (!strncmp(locale
, "it", 2))
1449 else if (!strncmp(locale
, "ja", 2))
1450 locale
= "Japanese";
1451 else if (!strncmp(locale
, "es", 2))
1453 else if (!strcmp(locale
, "zh_HK") || !strncasecmp(locale
, "zh-Hant", 7) || !strncasecmp(locale
, "zh_Hant", 7))
1456 * <rdar://problem/22130168>
1457 * <rdar://problem/27245567>
1459 * Try zh_TW first, then zh... Sigh...
1462 if (!access(CUPS_BUNDLEDIR
"/Resources/zh_TW.lproj/cups.strings", 0))
1467 else if (strstr(locale
, "_") != NULL
|| strstr(locale
, "-") != NULL
)
1470 * Drop country code, just try language...
1473 strlcpy(baselang
, locale
, sizeof(baselang
));
1474 if (baselang
[2] == '-' || baselang
[2] == '_')
1480 snprintf(filename
, sizeof(filename
),
1481 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1484 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename
));
1486 url
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
,
1488 (CFIndex
)strlen(filename
), false);
1491 stream
= CFReadStreamCreateWithFile(kCFAllocatorDefault
, url
);
1495 * Read the property list containing the localization data.
1497 * NOTE: This code currently generates a clang "potential leak"
1498 * warning, but the object is released in _cupsMessageFree().
1501 CFReadStreamOpen(stream
);
1504 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1505 kCFPropertyListImmutable
, NULL
,
1509 CFStringRef msg
= CFErrorCopyDescription(error
);
1512 CFStringGetCString(msg
, filename
, sizeof(filename
),
1513 kCFStringEncodingUTF8
);
1514 DEBUG_printf(("1appleMessageLoad: %s", filename
));
1521 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1522 kCFPropertyListImmutable
, NULL
,
1526 if (plist
&& CFGetTypeID(plist
) != CFDictionaryGetTypeID())
1538 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url
, stream
,
1542 * Create and return an empty array to act as a cache for messages, passing the
1543 * plist as the user data.
1546 return (_cupsMessageNew((void *)plist
));
1548 # endif /* CUPS_BUNDLEDIR */
1549 #endif /* __APPLE__ */
1553 * 'cups_cache_lookup()' - Lookup a language in the cache...
1556 static cups_lang_t
* /* O - Language data or NULL */
1558 const char *name
, /* I - Name of locale */
1559 cups_encoding_t encoding
) /* I - Encoding of locale */
1561 cups_lang_t
*lang
; /* Current language */
1564 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name
,
1565 encoding
, encoding
== CUPS_AUTO_ENCODING
? "auto" :
1566 lang_encodings
[encoding
]));
1569 * Loop through the cache and return a match if found...
1572 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
1574 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1575 "encoding=%d(%s)", (void *)lang
, lang
->language
, lang
->encoding
,
1576 lang_encodings
[lang
->encoding
]));
1578 if (!strcmp(lang
->language
, name
) &&
1579 (encoding
== CUPS_AUTO_ENCODING
|| encoding
== lang
->encoding
))
1583 DEBUG_puts("8cups_cache_lookup: returning match!");
1589 DEBUG_puts("8cups_cache_lookup: returning NULL!");
1596 * 'cups_message_compare()' - Compare two messages.
1599 static int /* O - Result of comparison */
1600 cups_message_compare(
1601 _cups_message_t
*m1
, /* I - First message */
1602 _cups_message_t
*m2
) /* I - Second message */
1604 return (strcmp(m1
->id
, m2
->id
));
1609 * 'cups_message_free()' - Free a message.
1613 cups_message_free(_cups_message_t
*m
) /* I - Message */
1626 * 'cups_message_load()' - Load the message catalog for a language.
1630 cups_message_load(cups_lang_t
*lang
) /* I - Language */
1632 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1633 lang
->strings
= appleMessageLoad(lang
->language
);
1636 char filename
[1024]; /* Filename for language locale file */
1637 _cups_globals_t
*cg
= _cupsGlobals();
1638 /* Pointer to library globals */
1641 snprintf(filename
, sizeof(filename
), "%s/%s/cups_%s.po", cg
->localedir
,
1642 lang
->language
, lang
->language
);
1644 if (strchr(lang
->language
, '_') && access(filename
, 0))
1647 * Country localization not available, look for generic localization...
1650 snprintf(filename
, sizeof(filename
), "%s/%.2s/cups_%.2s.po", cg
->localedir
,
1651 lang
->language
, lang
->language
);
1653 if (access(filename
, 0))
1656 * No generic localization, so use POSIX...
1659 DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename
,
1662 snprintf(filename
, sizeof(filename
), "%s/C/cups_C.po", cg
->localedir
);
1667 * Read the strings from the file...
1670 lang
->strings
= _cupsMessageLoad(filename
, 1);
1671 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1676 * 'cups_unquote()' - Unquote characters in strings...
1680 cups_unquote(char *d
, /* O - Unquoted string */
1681 const char *s
) /* I - Original string */
1694 *d
= *d
* 8 + *s
- '0';