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 */
260 char temp
[1024]; /* Temporary string */
263 if (!CFStringGetCString(languageName
, temp
, (CFIndex
)sizeof(temp
), kCFStringEncodingASCII
))
266 DEBUG_printf(("_cupsAppleLocale(languageName=%p(%s), locale=%p, localsize=%d)", (void *)languageName
, temp
, (void *)locale
, (int)localesize
));
269 localeName
= CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault
, languageName
);
274 * Copy the locale name and tweak as needed...
277 if (!CFStringGetCString(localeName
, locale
, (CFIndex
)localesize
, kCFStringEncodingASCII
))
280 DEBUG_printf(("_cupsAppleLocale: locale=\"%s\"", locale
));
282 CFRelease(localeName
);
285 * Map new language identifiers to locales...
289 i
< (int)(sizeof(apple_language_locale
) /
290 sizeof(apple_language_locale
[0]));
293 size_t len
= strlen(apple_language_locale
[i
].language
);
295 if (!strcmp(locale
, apple_language_locale
[i
].language
) ||
296 (!strncmp(locale
, apple_language_locale
[i
].language
, len
) && (locale
[len
] == '_' || locale
[len
] == '-')))
298 DEBUG_printf(("_cupsAppleLocale: Updating locale to \"%s\".", apple_language_locale
[i
].locale
));
299 strlcpy(locale
, apple_language_locale
[i
].locale
, localesize
);
307 * Just try the Apple language name...
310 if (!CFStringGetCString(languageName
, locale
, (CFIndex
)localesize
, kCFStringEncodingASCII
))
316 DEBUG_puts("_cupsAppleLocale: Returning NULL.");
321 * Convert language subtag into region subtag...
324 if (locale
[2] == '-')
326 else if (locale
[3] == '-')
329 if (!strchr(locale
, '.'))
330 strlcat(locale
, ".UTF-8", localesize
);
332 DEBUG_printf(("_cupsAppleLocale: Returning \"%s\".", locale
));
336 #endif /* __APPLE__ */
340 * '_cupsEncodingName()' - Return the character encoding name string
341 * for the given encoding enumeration.
344 const char * /* O - Character encoding */
346 cups_encoding_t encoding
) /* I - Encoding value */
348 if (encoding
< CUPS_US_ASCII
||
349 encoding
>= (cups_encoding_t
)(sizeof(lang_encodings
) / sizeof(lang_encodings
[0])))
351 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
352 encoding
, lang_encodings
[0]));
353 return (lang_encodings
[0]);
357 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
358 encoding
, lang_encodings
[encoding
]));
359 return (lang_encodings
[encoding
]);
365 * 'cupsLangDefault()' - Return the default language.
368 cups_lang_t
* /* O - Language data */
369 cupsLangDefault(void)
371 return (cupsLangGet(NULL
));
376 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
377 * for the given language.
380 const char * /* O - Character encoding */
381 cupsLangEncoding(cups_lang_t
*lang
) /* I - Language data */
384 return ((char*)lang_encodings
[0]);
386 return ((char*)lang_encodings
[lang
->encoding
]);
391 * 'cupsLangFlush()' - Flush all language data out of the cache.
397 cups_lang_t
*lang
, /* Current language */
398 *next
; /* Next language */
402 * Free all languages in the cache...
405 _cupsMutexLock(&lang_mutex
);
407 for (lang
= lang_cache
; lang
!= NULL
; lang
= next
)
410 * Free all messages...
413 _cupsMessageFree(lang
->strings
);
416 * Then free the language structure itself...
425 _cupsMutexUnlock(&lang_mutex
);
430 * 'cupsLangFree()' - Free language data.
432 * This does not actually free anything; use @link cupsLangFlush@ for that.
436 cupsLangFree(cups_lang_t
*lang
) /* I - Language to free */
438 _cupsMutexLock(&lang_mutex
);
440 if (lang
!= NULL
&& lang
->used
> 0)
443 _cupsMutexUnlock(&lang_mutex
);
448 * 'cupsLangGet()' - Get a language.
451 cups_lang_t
* /* O - Language data */
452 cupsLangGet(const char *language
) /* I - Language or locale */
454 int i
; /* Looping var */
456 char locale
[255]; /* Copy of locale name */
457 #endif /* !__APPLE__ */
458 char langname
[16], /* Requested language name */
459 country
[16], /* Country code */
460 charset
[16], /* Character set */
461 *csptr
, /* Pointer to CODESET string */
462 *ptr
, /* Pointer into language/charset */
463 real
[48]; /* Real language name */
464 cups_encoding_t encoding
; /* Encoding to use */
465 cups_lang_t
*lang
; /* Current language... */
466 static const char * const locale_encodings
[] =
467 { /* Locale charset names */
468 "ASCII", "ISO88591", "ISO88592", "ISO88593",
469 "ISO88594", "ISO88595", "ISO88596", "ISO88597",
470 "ISO88598", "ISO88599", "ISO885910", "UTF8",
471 "ISO885913", "ISO885914", "ISO885915", "CP874",
472 "CP1250", "CP1251", "CP1252", "CP1253",
473 "CP1254", "CP1255", "CP1256", "CP1257",
474 "CP1258", "KOI8R", "KOI8U", "ISO885911",
475 "ISO885916", "MACROMAN", "", "",
486 "CP932", "CP936", "CP949", "CP950",
487 "CP1361", "", "", "",
504 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
509 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language
));
513 * Set the character set to UTF-8...
516 strlcpy(charset
, "UTF8", sizeof(charset
));
519 * Apple's setlocale doesn't give us the user's localization
520 * preference so we have to look it up this way...
525 if (!getenv("SOFTWARE") || (language
= getenv("LANG")) == NULL
)
526 language
= appleLangDefault();
528 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language
));
533 * Set the charset to "unknown"...
539 * Use setlocale() to determine the currently set locale, and then
540 * fallback to environment variables to avoid setting the locale,
541 * since setlocale() is not thread-safe!
547 * First see if the locale has been set; if it is still "C" or
548 * "POSIX", use the environment to get the default...
552 ptr
= setlocale(LC_MESSAGES
, NULL
);
554 ptr
= setlocale(LC_ALL
, NULL
);
555 # endif /* LC_MESSAGES */
557 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr
));
559 if (!ptr
|| !strcmp(ptr
, "C") || !strcmp(ptr
, "POSIX"))
562 * Get the character set from the LC_CTYPE locale setting...
565 if ((ptr
= getenv("LC_CTYPE")) == NULL
)
566 if ((ptr
= getenv("LC_ALL")) == NULL
)
567 if ((ptr
= getenv("LANG")) == NULL
)
570 if ((csptr
= strchr(ptr
, '.')) != NULL
)
573 * Extract the character set from the environment...
576 for (ptr
= charset
, csptr
++; *csptr
; csptr
++)
577 if (ptr
< (charset
+ sizeof(charset
) - 1) && _cups_isalnum(*csptr
))
584 * Get the locale for messages from the LC_MESSAGES locale setting...
587 if ((ptr
= getenv("LC_MESSAGES")) == NULL
)
588 if ((ptr
= getenv("LC_ALL")) == NULL
)
589 if ((ptr
= getenv("LANG")) == NULL
)
595 strlcpy(locale
, ptr
, sizeof(locale
));
599 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
602 if (!strncmp(locale
, "nb", 2))
605 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language
));
608 #endif /* __APPLE__ */
611 * If "language" is NULL at this point, then chances are we are using
612 * a language that is not installed for the base OS.
618 * Switch to the POSIX ("C") locale...
626 * On systems that support the nl_langinfo(CODESET) call, use
627 * this value as the character set...
630 if (!charset
[0] && (csptr
= nl_langinfo(CODESET
)) != NULL
)
633 * Copy all of the letters and numbers in the CODESET string...
636 for (ptr
= charset
; *csptr
; csptr
++)
637 if (_cups_isalnum(*csptr
) && ptr
< (charset
+ sizeof(charset
) - 1))
642 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
643 "nl_langinfo(CODESET)...", charset
));
648 * If we don't have a character set by now, default to UTF-8...
652 strlcpy(charset
, "UTF8", sizeof(charset
));
655 * Parse the language string passed in to a locale string. "C" is the
656 * standard POSIX locale and is copied unchanged. Otherwise the
657 * language string is converted from ll-cc[.charset] (language-country)
658 * to ll_CC[.CHARSET] to match the file naming convention used by all
659 * POSIX-compliant operating systems. Invalid language names are mapped
660 * to the POSIX locale.
665 if (language
== NULL
|| !language
[0] ||
666 !strcmp(language
, "POSIX"))
667 strlcpy(langname
, "C", sizeof(langname
));
671 * Copy the parts of the locale string over safely...
674 for (ptr
= langname
; *language
; language
++)
675 if (*language
== '_' || *language
== '-' || *language
== '.')
677 else if (ptr
< (langname
+ sizeof(langname
) - 1))
678 *ptr
++ = (char)tolower(*language
& 255);
682 if (*language
== '_' || *language
== '-')
685 * Copy the country code...
688 for (language
++, ptr
= country
; *language
; language
++)
689 if (*language
== '.')
691 else if (ptr
< (country
+ sizeof(country
) - 1))
692 *ptr
++ = (char)toupper(*language
& 255);
697 * Map Chinese region codes to legacy country codes.
700 if (!strcmp(language
, "zh") && !strcmp(country
, "HANS"))
701 strlcpy(country
, "CN", sizeof(country
));
702 if (!strcmp(language
, "zh") && !strcmp(country
, "HANT"))
703 strlcpy(country
, "TW", sizeof(country
));
706 if (*language
== '.' && !charset
[0])
709 * Copy the encoding...
712 for (language
++, ptr
= charset
; *language
; language
++)
713 if (_cups_isalnum(*language
) && ptr
< (charset
+ sizeof(charset
) - 1))
714 *ptr
++ = (char)toupper(*language
& 255);
720 * Force a POSIX locale for an invalid language name...
723 if (strlen(langname
) != 2 && strlen(langname
) != 3)
725 strlcpy(langname
, "C", sizeof(langname
));
731 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
732 langname
, country
, charset
));
735 * Figure out the desired encoding...
738 encoding
= CUPS_AUTO_ENCODING
;
743 i
< (int)(sizeof(locale_encodings
) / sizeof(locale_encodings
[0]));
745 if (!_cups_strcasecmp(charset
, locale_encodings
[i
]))
747 encoding
= (cups_encoding_t
)i
;
751 if (encoding
== CUPS_AUTO_ENCODING
)
754 * Map alternate names for various character sets...
757 if (!_cups_strcasecmp(charset
, "iso-2022-jp") ||
758 !_cups_strcasecmp(charset
, "sjis"))
759 encoding
= CUPS_WINDOWS_932
;
760 else if (!_cups_strcasecmp(charset
, "iso-2022-cn"))
761 encoding
= CUPS_WINDOWS_936
;
762 else if (!_cups_strcasecmp(charset
, "iso-2022-kr"))
763 encoding
= CUPS_WINDOWS_949
;
764 else if (!_cups_strcasecmp(charset
, "big5"))
765 encoding
= CUPS_WINDOWS_950
;
769 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding
,
770 encoding
== CUPS_AUTO_ENCODING
? "auto" :
771 lang_encodings
[encoding
]));
774 * See if we already have this language/country loaded...
778 snprintf(real
, sizeof(real
), "%s_%s", langname
, country
);
780 strlcpy(real
, langname
, sizeof(real
));
782 _cupsMutexLock(&lang_mutex
);
784 if ((lang
= cups_cache_lookup(real
, encoding
)) != NULL
)
786 _cupsMutexUnlock(&lang_mutex
);
788 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real
));
794 * See if there is a free language available; if so, use that
798 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
805 * Allocate memory for the language and add it to the cache.
808 if ((lang
= calloc(sizeof(cups_lang_t
), 1)) == NULL
)
810 _cupsMutexUnlock(&lang_mutex
);
815 lang
->next
= lang_cache
;
821 * Free all old strings as needed...
824 _cupsMessageFree(lang
->strings
);
825 lang
->strings
= NULL
;
829 * Then assign the language and encoding fields...
833 strlcpy(lang
->language
, real
, sizeof(lang
->language
));
835 if (encoding
!= CUPS_AUTO_ENCODING
)
836 lang
->encoding
= encoding
;
838 lang
->encoding
= CUPS_UTF8
;
844 _cupsMutexUnlock(&lang_mutex
);
851 * '_cupsLangString()' - Get a message string.
853 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
854 * convert the string to the language encoding.
857 const char * /* O - Localized message */
858 _cupsLangString(cups_lang_t
*lang
, /* I - Language */
859 const char *message
) /* I - Message */
861 const char *s
; /* Localized message */
864 DEBUG_printf(("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang
, message
));
867 * Range check input...
870 if (!lang
|| !message
|| !*message
)
873 _cupsMutexLock(&lang_mutex
);
876 * Load the message catalog if needed...
880 cups_message_load(lang
);
882 s
= _cupsMessageLookup(lang
->strings
, message
);
884 _cupsMutexUnlock(&lang_mutex
);
891 * '_cupsMessageFree()' - Free a messages array.
895 _cupsMessageFree(cups_array_t
*a
) /* I - Message array */
897 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
899 * Release the cups.strings dictionary as needed...
902 if (cupsArrayUserData(a
))
903 CFRelease((CFDictionaryRef
)cupsArrayUserData(a
));
904 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
915 * '_cupsMessageLoad()' - Load a .po file into a messages array.
918 cups_array_t
* /* O - New message array */
919 _cupsMessageLoad(const char *filename
, /* I - Message catalog to load */
920 int unquote
) /* I - Unescape \foo in strings? */
922 cups_file_t
*fp
; /* Message file */
923 cups_array_t
*a
; /* Message array */
924 _cups_message_t
*m
; /* Current message */
925 char s
[4096], /* String buffer */
926 *ptr
, /* Pointer into buffer */
927 *temp
; /* New string */
928 size_t length
, /* Length of combined strings */
929 ptrlen
; /* Length of string */
932 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename
));
935 * Create an array to hold the messages...
938 if ((a
= _cupsMessageNew(NULL
)) == NULL
)
940 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
945 * Open the message catalog file...
948 if ((fp
= cupsFileOpen(filename
, "r")) == NULL
)
950 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
956 * Read messages from the catalog file until EOF...
958 * The format is the GNU gettext .po format, which is fairly simple:
961 * msgstr "localized text"
963 * The ID and localized text can span multiple lines using the form:
968 * "localized text spanning "
974 while (cupsFileGets(fp
, s
, sizeof(s
)) != NULL
)
977 * Skip blank and comment lines...
980 if (s
[0] == '#' || !s
[0])
984 * Strip the trailing quote...
987 if ((ptr
= strrchr(s
, '\"')) == NULL
)
993 * Find start of value...
996 if ((ptr
= strchr(s
, '\"')) == NULL
)
1002 * Unquote the text...
1006 cups_unquote(ptr
, ptr
);
1009 * Create or add to a message...
1012 if (!strncmp(s
, "msgid", 5))
1015 * Add previous message as needed...
1020 if (m
->str
&& m
->str
[0])
1027 * Translation is empty, don't add it... (STR #4033)
1038 * Create a new message with the given msgid string...
1041 if ((m
= (_cups_message_t
*)calloc(1, sizeof(_cups_message_t
))) == NULL
)
1047 if ((m
->id
= strdup(ptr
)) == NULL
)
1054 else if (s
[0] == '\"' && m
)
1057 * Append to current string...
1060 length
= strlen(m
->str
? m
->str
: m
->id
);
1061 ptrlen
= strlen(ptr
);
1063 if ((temp
= realloc(m
->str
? m
->str
: m
->id
, length
+ ptrlen
+ 1)) == NULL
)
1077 * Copy the new portion to the end of the msgstr string - safe
1078 * to use memcpy because the buffer is allocated to the correct
1084 memcpy(m
->str
+ length
, ptr
, ptrlen
+ 1);
1089 * Copy the new portion to the end of the msgid string - safe
1090 * to use memcpy because the buffer is allocated to the correct
1096 memcpy(m
->id
+ length
, ptr
, ptrlen
+ 1);
1099 else if (!strncmp(s
, "msgstr", 6) && m
)
1105 if ((m
->str
= strdup(ptr
)) == NULL
)
1117 * Add the last message string to the array as needed...
1122 if (m
->str
&& m
->str
[0])
1129 * Translation is empty, don't add it... (STR #4033)
1140 * Close the message catalog file and return the new array...
1145 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
1146 cupsArrayCount(a
)));
1153 * '_cupsMessageLookup()' - Lookup a message string.
1156 const char * /* O - Localized message */
1157 _cupsMessageLookup(cups_array_t
*a
, /* I - Message array */
1158 const char *m
) /* I - Message */
1160 _cups_message_t key
, /* Search key */
1161 *match
; /* Matching message */
1164 DEBUG_printf(("_cupsMessageLookup(a=%p, m=\"%s\")", (void *)a
, m
));
1167 * Lookup the message string; if it doesn't exist in the catalog,
1168 * then return the message that was passed to us...
1172 match
= (_cups_message_t
*)cupsArrayFind(a
, &key
);
1174 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1175 if (!match
&& cupsArrayUserData(a
))
1178 * Try looking the string up in the cups.strings dictionary...
1181 CFDictionaryRef dict
; /* cups.strings dictionary */
1182 CFStringRef cfm
, /* Message as a CF string */
1183 cfstr
; /* Localized text as a CF string */
1185 dict
= (CFDictionaryRef
)cupsArrayUserData(a
);
1186 cfm
= CFStringCreateWithCString(kCFAllocatorDefault
, m
,
1187 kCFStringEncodingUTF8
);
1188 match
= calloc(1, sizeof(_cups_message_t
));
1189 match
->id
= strdup(m
);
1190 cfstr
= cfm
? CFDictionaryGetValue(dict
, cfm
) : NULL
;
1194 char buffer
[1024]; /* Message buffer */
1196 CFStringGetCString(cfstr
, buffer
, sizeof(buffer
), kCFStringEncodingUTF8
);
1197 match
->str
= strdup(buffer
);
1199 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...",
1204 match
->str
= strdup(m
);
1206 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m
));
1209 cupsArrayAdd(a
, match
);
1214 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1216 if (match
&& match
->str
)
1217 return (match
->str
);
1224 * '_cupsMessageNew()' - Make a new message catalog array.
1227 cups_array_t
* /* O - Array */
1228 _cupsMessageNew(void *context
) /* I - User data */
1230 return (cupsArrayNew3((cups_array_func_t
)cups_message_compare
, context
,
1231 (cups_ahash_func_t
)NULL
, 0,
1232 (cups_acopy_func_t
)NULL
,
1233 (cups_afree_func_t
)cups_message_free
));
1239 * 'appleLangDefault()' - Get the default locale string.
1242 static const char * /* O - Locale string */
1243 appleLangDefault(void)
1245 CFBundleRef bundle
; /* Main bundle (if any) */
1246 CFArrayRef bundleList
; /* List of localizations in bundle */
1247 CFPropertyListRef localizationList
= NULL
;
1248 /* List of localization data */
1249 CFStringRef languageName
; /* Current name */
1250 char *lang
; /* LANG environment variable */
1251 _cups_globals_t
*cg
= _cupsGlobals();
1252 /* Pointer to library globals */
1255 DEBUG_puts("2appleLangDefault()");
1258 * Only do the lookup and translation the first time.
1261 if (!cg
->language
[0])
1263 if (getenv("SOFTWARE") != NULL
&& (lang
= getenv("LANG")) != NULL
)
1265 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang
));
1266 strlcpy(cg
->language
, lang
, sizeof(cg
->language
));
1267 return (cg
->language
);
1269 else if ((bundle
= CFBundleGetMainBundle()) != NULL
&&
1270 (bundleList
= CFBundleCopyBundleLocalizations(bundle
)) != NULL
)
1272 CFURLRef resources
= CFBundleCopyResourcesDirectoryURL(bundle
);
1274 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
1278 CFStringRef cfpath
= CFURLCopyPath(resources
);
1284 * See if we have an Info.plist file in the bundle...
1287 CFStringGetCString(cfpath
, path
, sizeof(path
), kCFStringEncodingUTF8
);
1288 DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path
));
1289 strlcat(path
, "Contents/Info.plist", sizeof(path
));
1291 if (!access(path
, R_OK
))
1292 localizationList
= CFBundleCopyPreferredLocalizationsFromArray(bundleList
);
1294 DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL...");
1299 CFRelease(resources
);
1302 DEBUG_puts("3appleLangDefault: No resource URL.");
1304 CFRelease(bundleList
);
1307 if (!localizationList
)
1309 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
1312 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1313 kCFPreferencesCurrentApplication
);
1316 if (localizationList
)
1319 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID())
1320 DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.",
1321 (int)CFArrayGetCount(localizationList
)));
1323 DEBUG_puts("3appleLangDefault: Got localizationList but not an array.");
1326 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID() &&
1327 CFArrayGetCount(localizationList
) > 0)
1329 languageName
= CFArrayGetValueAtIndex(localizationList
, 0);
1332 CFGetTypeID(languageName
) == CFStringGetTypeID())
1334 if (_cupsAppleLocale(languageName
, cg
->language
, sizeof(cg
->language
)))
1335 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
1338 DEBUG_puts("3appleLangDefault: Unable to get locale.");
1342 CFRelease(localizationList
);
1346 * If we didn't find the language, default to en_US...
1349 if (!cg
->language
[0])
1351 DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
1352 strlcpy(cg
->language
, "en_US.UTF-8", sizeof(cg
->language
));
1356 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg
->language
));
1359 * Return the cached locale...
1362 return (cg
->language
);
1366 # ifdef CUPS_BUNDLEDIR
1368 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1371 static cups_array_t
* /* O - Message catalog */
1372 appleMessageLoad(const char *locale
) /* I - Locale ID */
1374 char filename
[1024], /* Path to cups.strings file */
1375 applelang
[256], /* Apple language ID */
1376 baselang
[4]; /* Base language */
1377 CFURLRef url
; /* URL to cups.strings file */
1378 CFReadStreamRef stream
= NULL
; /* File stream */
1379 CFPropertyListRef plist
= NULL
; /* Localization file */
1381 const char *cups_strings
= getenv("CUPS_STRINGS");
1382 /* Test strings file */
1383 CFErrorRef error
= NULL
; /* Error when opening file */
1387 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale
));
1390 * Load the cups.strings file...
1396 DEBUG_puts("1appleMessageLoad: Using debug CUPS_STRINGS file.");
1397 strlcpy(filename
, cups_strings
, sizeof(filename
));
1402 snprintf(filename
, sizeof(filename
),
1403 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings",
1404 _cupsAppleLanguage(locale
, applelang
, sizeof(applelang
)));
1406 if (access(filename
, 0))
1409 * <rdar://problem/22086642>
1411 * Try with original locale string...
1414 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1415 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1418 if (access(filename
, 0))
1421 * <rdar://problem/25292403>
1423 * Try with just the language code...
1426 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1428 strlcpy(baselang
, locale
, sizeof(baselang
));
1429 if (baselang
[3] == '-' || baselang
[3] == '_')
1432 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", baselang
);
1435 if (access(filename
, 0))
1438 * Try alternate lproj directory names...
1441 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1443 if (!strncmp(locale
, "en", 2))
1445 else if (!strncmp(locale
, "nb", 2))
1447 else if (!strncmp(locale
, "nl", 2))
1449 else if (!strncmp(locale
, "fr", 2))
1451 else if (!strncmp(locale
, "de", 2))
1453 else if (!strncmp(locale
, "it", 2))
1455 else if (!strncmp(locale
, "ja", 2))
1456 locale
= "Japanese";
1457 else if (!strncmp(locale
, "es", 2))
1459 else if (!strcmp(locale
, "zh_HK") || !strncasecmp(locale
, "zh-Hant", 7) || !strncasecmp(locale
, "zh_Hant", 7))
1462 * <rdar://problem/22130168>
1463 * <rdar://problem/27245567>
1465 * Try zh_TW first, then zh... Sigh...
1468 if (!access(CUPS_BUNDLEDIR
"/Resources/zh_TW.lproj/cups.strings", 0))
1473 else if (strstr(locale
, "_") != NULL
|| strstr(locale
, "-") != NULL
)
1476 * Drop country code, just try language...
1479 strlcpy(baselang
, locale
, sizeof(baselang
));
1480 if (baselang
[2] == '-' || baselang
[2] == '_')
1486 snprintf(filename
, sizeof(filename
),
1487 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1490 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename
));
1492 url
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
,
1494 (CFIndex
)strlen(filename
), false);
1497 stream
= CFReadStreamCreateWithFile(kCFAllocatorDefault
, url
);
1501 * Read the property list containing the localization data.
1503 * NOTE: This code currently generates a clang "potential leak"
1504 * warning, but the object is released in _cupsMessageFree().
1507 CFReadStreamOpen(stream
);
1510 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1511 kCFPropertyListImmutable
, NULL
,
1515 CFStringRef msg
= CFErrorCopyDescription(error
);
1518 CFStringGetCString(msg
, filename
, sizeof(filename
),
1519 kCFStringEncodingUTF8
);
1520 DEBUG_printf(("1appleMessageLoad: %s", filename
));
1527 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1528 kCFPropertyListImmutable
, NULL
,
1532 if (plist
&& CFGetTypeID(plist
) != CFDictionaryGetTypeID())
1544 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url
, stream
,
1548 * Create and return an empty array to act as a cache for messages, passing the
1549 * plist as the user data.
1552 return (_cupsMessageNew((void *)plist
));
1554 # endif /* CUPS_BUNDLEDIR */
1555 #endif /* __APPLE__ */
1559 * 'cups_cache_lookup()' - Lookup a language in the cache...
1562 static cups_lang_t
* /* O - Language data or NULL */
1564 const char *name
, /* I - Name of locale */
1565 cups_encoding_t encoding
) /* I - Encoding of locale */
1567 cups_lang_t
*lang
; /* Current language */
1570 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name
,
1571 encoding
, encoding
== CUPS_AUTO_ENCODING
? "auto" :
1572 lang_encodings
[encoding
]));
1575 * Loop through the cache and return a match if found...
1578 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
1580 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1581 "encoding=%d(%s)", (void *)lang
, lang
->language
, lang
->encoding
,
1582 lang_encodings
[lang
->encoding
]));
1584 if (!strcmp(lang
->language
, name
) &&
1585 (encoding
== CUPS_AUTO_ENCODING
|| encoding
== lang
->encoding
))
1589 DEBUG_puts("8cups_cache_lookup: returning match!");
1595 DEBUG_puts("8cups_cache_lookup: returning NULL!");
1602 * 'cups_message_compare()' - Compare two messages.
1605 static int /* O - Result of comparison */
1606 cups_message_compare(
1607 _cups_message_t
*m1
, /* I - First message */
1608 _cups_message_t
*m2
) /* I - Second message */
1610 return (strcmp(m1
->id
, m2
->id
));
1615 * 'cups_message_free()' - Free a message.
1619 cups_message_free(_cups_message_t
*m
) /* I - Message */
1632 * 'cups_message_load()' - Load the message catalog for a language.
1636 cups_message_load(cups_lang_t
*lang
) /* I - Language */
1638 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1639 lang
->strings
= appleMessageLoad(lang
->language
);
1642 char filename
[1024]; /* Filename for language locale file */
1643 _cups_globals_t
*cg
= _cupsGlobals();
1644 /* Pointer to library globals */
1647 snprintf(filename
, sizeof(filename
), "%s/%s/cups_%s.po", cg
->localedir
,
1648 lang
->language
, lang
->language
);
1650 if (strchr(lang
->language
, '_') && access(filename
, 0))
1653 * Country localization not available, look for generic localization...
1656 snprintf(filename
, sizeof(filename
), "%s/%.2s/cups_%.2s.po", cg
->localedir
,
1657 lang
->language
, lang
->language
);
1659 if (access(filename
, 0))
1662 * No generic localization, so use POSIX...
1665 DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename
,
1668 snprintf(filename
, sizeof(filename
), "%s/C/cups_C.po", cg
->localedir
);
1673 * Read the strings from the file...
1676 lang
->strings
= _cupsMessageLoad(filename
, 1);
1677 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1682 * 'cups_unquote()' - Unquote characters in strings...
1686 cups_unquote(char *d
, /* O - Unquoted string */
1687 const char *s
) /* I - Original string */
1700 *d
= *d
* 8 + *s
- '0';