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)", languageName
, temp
, 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 if (*language
== '.' && !charset
[0])
700 * Copy the encoding...
703 for (language
++, ptr
= charset
; *language
; language
++)
704 if (_cups_isalnum(*language
) && ptr
< (charset
+ sizeof(charset
) - 1))
705 *ptr
++ = (char)toupper(*language
& 255);
711 * Force a POSIX locale for an invalid language name...
714 if (strlen(langname
) != 2 && strlen(langname
) != 3)
716 strlcpy(langname
, "C", sizeof(langname
));
722 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
723 langname
, country
, charset
));
726 * Figure out the desired encoding...
729 encoding
= CUPS_AUTO_ENCODING
;
734 i
< (int)(sizeof(locale_encodings
) / sizeof(locale_encodings
[0]));
736 if (!_cups_strcasecmp(charset
, locale_encodings
[i
]))
738 encoding
= (cups_encoding_t
)i
;
742 if (encoding
== CUPS_AUTO_ENCODING
)
745 * Map alternate names for various character sets...
748 if (!_cups_strcasecmp(charset
, "iso-2022-jp") ||
749 !_cups_strcasecmp(charset
, "sjis"))
750 encoding
= CUPS_WINDOWS_932
;
751 else if (!_cups_strcasecmp(charset
, "iso-2022-cn"))
752 encoding
= CUPS_WINDOWS_936
;
753 else if (!_cups_strcasecmp(charset
, "iso-2022-kr"))
754 encoding
= CUPS_WINDOWS_949
;
755 else if (!_cups_strcasecmp(charset
, "big5"))
756 encoding
= CUPS_WINDOWS_950
;
760 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding
,
761 encoding
== CUPS_AUTO_ENCODING
? "auto" :
762 lang_encodings
[encoding
]));
765 * See if we already have this language/country loaded...
769 snprintf(real
, sizeof(real
), "%s_%s", langname
, country
);
771 strlcpy(real
, langname
, sizeof(real
));
773 _cupsMutexLock(&lang_mutex
);
775 if ((lang
= cups_cache_lookup(real
, encoding
)) != NULL
)
777 _cupsMutexUnlock(&lang_mutex
);
779 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real
));
785 * See if there is a free language available; if so, use that
789 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
796 * Allocate memory for the language and add it to the cache.
799 if ((lang
= calloc(sizeof(cups_lang_t
), 1)) == NULL
)
801 _cupsMutexUnlock(&lang_mutex
);
806 lang
->next
= lang_cache
;
812 * Free all old strings as needed...
815 _cupsMessageFree(lang
->strings
);
816 lang
->strings
= NULL
;
820 * Then assign the language and encoding fields...
824 strlcpy(lang
->language
, real
, sizeof(lang
->language
));
826 if (encoding
!= CUPS_AUTO_ENCODING
)
827 lang
->encoding
= encoding
;
829 lang
->encoding
= CUPS_UTF8
;
835 _cupsMutexUnlock(&lang_mutex
);
842 * '_cupsLangString()' - Get a message string.
844 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
845 * convert the string to the language encoding.
848 const char * /* O - Localized message */
849 _cupsLangString(cups_lang_t
*lang
, /* I - Language */
850 const char *message
) /* I - Message */
852 const char *s
; /* Localized message */
855 DEBUG_printf(("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang
, message
));
858 * Range check input...
861 if (!lang
|| !message
|| !*message
)
864 _cupsMutexLock(&lang_mutex
);
867 * Load the message catalog if needed...
871 cups_message_load(lang
);
873 s
= _cupsMessageLookup(lang
->strings
, message
);
875 _cupsMutexUnlock(&lang_mutex
);
882 * '_cupsMessageFree()' - Free a messages array.
886 _cupsMessageFree(cups_array_t
*a
) /* I - Message array */
888 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
890 * Release the cups.strings dictionary as needed...
893 if (cupsArrayUserData(a
))
894 CFRelease((CFDictionaryRef
)cupsArrayUserData(a
));
895 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
906 * '_cupsMessageLoad()' - Load a .po file into a messages array.
909 cups_array_t
* /* O - New message array */
910 _cupsMessageLoad(const char *filename
, /* I - Message catalog to load */
911 int unquote
) /* I - Unescape \foo in strings? */
913 cups_file_t
*fp
; /* Message file */
914 cups_array_t
*a
; /* Message array */
915 _cups_message_t
*m
; /* Current message */
916 char s
[4096], /* String buffer */
917 *ptr
, /* Pointer into buffer */
918 *temp
; /* New string */
919 size_t length
, /* Length of combined strings */
920 ptrlen
; /* Length of string */
923 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename
));
926 * Create an array to hold the messages...
929 if ((a
= _cupsMessageNew(NULL
)) == NULL
)
931 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
936 * Open the message catalog file...
939 if ((fp
= cupsFileOpen(filename
, "r")) == NULL
)
941 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
947 * Read messages from the catalog file until EOF...
949 * The format is the GNU gettext .po format, which is fairly simple:
952 * msgstr "localized text"
954 * The ID and localized text can span multiple lines using the form:
959 * "localized text spanning "
965 while (cupsFileGets(fp
, s
, sizeof(s
)) != NULL
)
968 * Skip blank and comment lines...
971 if (s
[0] == '#' || !s
[0])
975 * Strip the trailing quote...
978 if ((ptr
= strrchr(s
, '\"')) == NULL
)
984 * Find start of value...
987 if ((ptr
= strchr(s
, '\"')) == NULL
)
993 * Unquote the text...
997 cups_unquote(ptr
, ptr
);
1000 * Create or add to a message...
1003 if (!strncmp(s
, "msgid", 5))
1006 * Add previous message as needed...
1011 if (m
->str
&& m
->str
[0])
1018 * Translation is empty, don't add it... (STR #4033)
1029 * Create a new message with the given msgid string...
1032 if ((m
= (_cups_message_t
*)calloc(1, sizeof(_cups_message_t
))) == NULL
)
1038 if ((m
->id
= strdup(ptr
)) == NULL
)
1045 else if (s
[0] == '\"' && m
)
1048 * Append to current string...
1051 length
= strlen(m
->str
? m
->str
: m
->id
);
1052 ptrlen
= strlen(ptr
);
1054 if ((temp
= realloc(m
->str
? m
->str
: m
->id
, length
+ ptrlen
+ 1)) == NULL
)
1068 * Copy the new portion to the end of the msgstr string - safe
1069 * to use memcpy because the buffer is allocated to the correct
1075 memcpy(m
->str
+ length
, ptr
, ptrlen
+ 1);
1080 * Copy the new portion to the end of the msgid string - safe
1081 * to use memcpy because the buffer is allocated to the correct
1087 memcpy(m
->id
+ length
, ptr
, ptrlen
+ 1);
1090 else if (!strncmp(s
, "msgstr", 6) && m
)
1096 if ((m
->str
= strdup(ptr
)) == NULL
)
1108 * Add the last message string to the array as needed...
1113 if (m
->str
&& m
->str
[0])
1120 * Translation is empty, don't add it... (STR #4033)
1131 * Close the message catalog file and return the new array...
1136 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
1137 cupsArrayCount(a
)));
1144 * '_cupsMessageLookup()' - Lookup a message string.
1147 const char * /* O - Localized message */
1148 _cupsMessageLookup(cups_array_t
*a
, /* I - Message array */
1149 const char *m
) /* I - Message */
1151 _cups_message_t key
, /* Search key */
1152 *match
; /* Matching message */
1155 DEBUG_printf(("_cupsMessageLookup(a=%p, m=\"%s\")", (void *)a
, m
));
1158 * Lookup the message string; if it doesn't exist in the catalog,
1159 * then return the message that was passed to us...
1163 match
= (_cups_message_t
*)cupsArrayFind(a
, &key
);
1165 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1166 if (!match
&& cupsArrayUserData(a
))
1169 * Try looking the string up in the cups.strings dictionary...
1172 CFDictionaryRef dict
; /* cups.strings dictionary */
1173 CFStringRef cfm
, /* Message as a CF string */
1174 cfstr
; /* Localized text as a CF string */
1176 dict
= (CFDictionaryRef
)cupsArrayUserData(a
);
1177 cfm
= CFStringCreateWithCString(kCFAllocatorDefault
, m
,
1178 kCFStringEncodingUTF8
);
1179 match
= calloc(1, sizeof(_cups_message_t
));
1180 match
->id
= strdup(m
);
1181 cfstr
= cfm
? CFDictionaryGetValue(dict
, cfm
) : NULL
;
1185 char buffer
[1024]; /* Message buffer */
1187 CFStringGetCString(cfstr
, buffer
, sizeof(buffer
), kCFStringEncodingUTF8
);
1188 match
->str
= strdup(buffer
);
1190 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...",
1195 match
->str
= strdup(m
);
1197 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m
));
1200 cupsArrayAdd(a
, match
);
1205 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1207 if (match
&& match
->str
)
1208 return (match
->str
);
1215 * '_cupsMessageNew()' - Make a new message catalog array.
1218 cups_array_t
* /* O - Array */
1219 _cupsMessageNew(void *context
) /* I - User data */
1221 return (cupsArrayNew3((cups_array_func_t
)cups_message_compare
, context
,
1222 (cups_ahash_func_t
)NULL
, 0,
1223 (cups_acopy_func_t
)NULL
,
1224 (cups_afree_func_t
)cups_message_free
));
1230 * 'appleLangDefault()' - Get the default locale string.
1233 static const char * /* O - Locale string */
1234 appleLangDefault(void)
1236 CFBundleRef bundle
; /* Main bundle (if any) */
1237 CFArrayRef bundleList
; /* List of localizations in bundle */
1238 CFPropertyListRef localizationList
= NULL
;
1239 /* List of localization data */
1240 CFStringRef languageName
; /* Current name */
1241 char *lang
; /* LANG environment variable */
1242 _cups_globals_t
*cg
= _cupsGlobals();
1243 /* Pointer to library globals */
1246 DEBUG_puts("2appleLangDefault()");
1249 * Only do the lookup and translation the first time.
1252 if (!cg
->language
[0])
1254 if (getenv("SOFTWARE") != NULL
&& (lang
= getenv("LANG")) != NULL
)
1256 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang
));
1257 strlcpy(cg
->language
, lang
, sizeof(cg
->language
));
1258 return (cg
->language
);
1260 else if ((bundle
= CFBundleGetMainBundle()) != NULL
&&
1261 (bundleList
= CFBundleCopyBundleLocalizations(bundle
)) != NULL
)
1263 CFURLRef resources
= CFBundleCopyResourcesDirectoryURL(bundle
);
1265 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
1269 CFStringRef cfpath
= CFURLCopyPath(resources
);
1275 * See if we have an Info.plist file in the bundle...
1278 CFStringGetCString(cfpath
, path
, sizeof(path
), kCFStringEncodingUTF8
);
1279 DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path
));
1280 strlcat(path
, "Contents/Info.plist", sizeof(path
));
1282 if (!access(path
, R_OK
))
1283 localizationList
= CFBundleCopyPreferredLocalizationsFromArray(bundleList
);
1285 DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL...");
1290 CFRelease(resources
);
1293 DEBUG_puts("3appleLangDefault: No resource URL.");
1295 CFRelease(bundleList
);
1298 if (!localizationList
)
1300 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
1303 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1304 kCFPreferencesCurrentApplication
);
1307 if (localizationList
)
1310 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID())
1311 DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.",
1312 (int)CFArrayGetCount(localizationList
)));
1314 DEBUG_puts("3appleLangDefault: Got localizationList but not an array.");
1317 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID() &&
1318 CFArrayGetCount(localizationList
) > 0)
1320 languageName
= CFArrayGetValueAtIndex(localizationList
, 0);
1323 CFGetTypeID(languageName
) == CFStringGetTypeID())
1325 if (_cupsAppleLocale(languageName
, cg
->language
, sizeof(cg
->language
)))
1326 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
1329 DEBUG_puts("3appleLangDefault: Unable to get locale.");
1333 CFRelease(localizationList
);
1337 * If we didn't find the language, default to en_US...
1340 if (!cg
->language
[0])
1342 DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
1343 strlcpy(cg
->language
, "en_US.UTF-8", sizeof(cg
->language
));
1347 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg
->language
));
1350 * Return the cached locale...
1353 return (cg
->language
);
1357 # ifdef CUPS_BUNDLEDIR
1359 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1362 static cups_array_t
* /* O - Message catalog */
1363 appleMessageLoad(const char *locale
) /* I - Locale ID */
1365 char filename
[1024], /* Path to cups.strings file */
1366 applelang
[256], /* Apple language ID */
1367 baselang
[4]; /* Base language */
1368 CFURLRef url
; /* URL to cups.strings file */
1369 CFReadStreamRef stream
= NULL
; /* File stream */
1370 CFPropertyListRef plist
= NULL
; /* Localization file */
1372 const char *cups_strings
= getenv("CUPS_STRINGS");
1373 /* Test strings file */
1374 CFErrorRef error
= NULL
; /* Error when opening file */
1378 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale
));
1381 * Load the cups.strings file...
1387 DEBUG_puts("1appleMessageLoad: Using debug CUPS_STRINGS file.");
1388 strlcpy(filename
, cups_strings
, sizeof(filename
));
1393 snprintf(filename
, sizeof(filename
),
1394 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings",
1395 _cupsAppleLanguage(locale
, applelang
, sizeof(applelang
)));
1397 if (access(filename
, 0))
1400 * <rdar://problem/22086642>
1402 * Try with original locale string...
1405 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1406 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1409 if (access(filename
, 0))
1412 * <rdar://problem/25292403>
1414 * Try with just the language code...
1417 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1419 strlcpy(baselang
, locale
, sizeof(baselang
));
1420 if (baselang
[3] == '-' || baselang
[3] == '_')
1423 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", baselang
);
1426 if (access(filename
, 0))
1429 * Try alternate lproj directory names...
1432 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1434 if (!strncmp(locale
, "en", 2))
1436 else if (!strncmp(locale
, "nb", 2))
1438 else if (!strncmp(locale
, "nl", 2))
1440 else if (!strncmp(locale
, "fr", 2))
1442 else if (!strncmp(locale
, "de", 2))
1444 else if (!strncmp(locale
, "it", 2))
1446 else if (!strncmp(locale
, "ja", 2))
1447 locale
= "Japanese";
1448 else if (!strncmp(locale
, "es", 2))
1450 else if (!strcmp(locale
, "zh_HK") || !strncasecmp(locale
, "zh-Hant", 7) || !strncasecmp(locale
, "zh_Hant", 7))
1453 * <rdar://problem/22130168>
1454 * <rdar://problem/27245567>
1456 * Try zh_TW first, then zh... Sigh...
1459 if (!access(CUPS_BUNDLEDIR
"/Resources/zh_TW.lproj/cups.strings", 0))
1464 else if (strstr(locale
, "_") != NULL
|| strstr(locale
, "-") != NULL
)
1467 * Drop country code, just try language...
1470 strlcpy(baselang
, locale
, sizeof(baselang
));
1471 if (baselang
[3] == '-' || baselang
[3] == '_')
1477 snprintf(filename
, sizeof(filename
),
1478 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1481 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename
));
1483 url
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
,
1485 (CFIndex
)strlen(filename
), false);
1488 stream
= CFReadStreamCreateWithFile(kCFAllocatorDefault
, url
);
1492 * Read the property list containing the localization data.
1494 * NOTE: This code currently generates a clang "potential leak"
1495 * warning, but the object is released in _cupsMessageFree().
1498 CFReadStreamOpen(stream
);
1501 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1502 kCFPropertyListImmutable
, NULL
,
1506 CFStringRef msg
= CFErrorCopyDescription(error
);
1509 CFStringGetCString(msg
, filename
, sizeof(filename
),
1510 kCFStringEncodingUTF8
);
1511 DEBUG_printf(("1appleMessageLoad: %s", filename
));
1518 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1519 kCFPropertyListImmutable
, NULL
,
1523 if (plist
&& CFGetTypeID(plist
) != CFDictionaryGetTypeID())
1535 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url
, stream
,
1539 * Create and return an empty array to act as a cache for messages, passing the
1540 * plist as the user data.
1543 return (_cupsMessageNew((void *)plist
));
1545 # endif /* CUPS_BUNDLEDIR */
1546 #endif /* __APPLE__ */
1550 * 'cups_cache_lookup()' - Lookup a language in the cache...
1553 static cups_lang_t
* /* O - Language data or NULL */
1555 const char *name
, /* I - Name of locale */
1556 cups_encoding_t encoding
) /* I - Encoding of locale */
1558 cups_lang_t
*lang
; /* Current language */
1561 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name
,
1562 encoding
, encoding
== CUPS_AUTO_ENCODING
? "auto" :
1563 lang_encodings
[encoding
]));
1566 * Loop through the cache and return a match if found...
1569 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
1571 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1572 "encoding=%d(%s)", (void *)lang
, lang
->language
, lang
->encoding
,
1573 lang_encodings
[lang
->encoding
]));
1575 if (!strcmp(lang
->language
, name
) &&
1576 (encoding
== CUPS_AUTO_ENCODING
|| encoding
== lang
->encoding
))
1580 DEBUG_puts("8cups_cache_lookup: returning match!");
1586 DEBUG_puts("8cups_cache_lookup: returning NULL!");
1593 * 'cups_message_compare()' - Compare two messages.
1596 static int /* O - Result of comparison */
1597 cups_message_compare(
1598 _cups_message_t
*m1
, /* I - First message */
1599 _cups_message_t
*m2
) /* I - Second message */
1601 return (strcmp(m1
->id
, m2
->id
));
1606 * 'cups_message_free()' - Free a message.
1610 cups_message_free(_cups_message_t
*m
) /* I - Message */
1623 * 'cups_message_load()' - Load the message catalog for a language.
1627 cups_message_load(cups_lang_t
*lang
) /* I - Language */
1629 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1630 lang
->strings
= appleMessageLoad(lang
->language
);
1633 char filename
[1024]; /* Filename for language locale file */
1634 _cups_globals_t
*cg
= _cupsGlobals();
1635 /* Pointer to library globals */
1638 snprintf(filename
, sizeof(filename
), "%s/%s/cups_%s.po", cg
->localedir
,
1639 lang
->language
, lang
->language
);
1641 if (strchr(lang
->language
, '_') && access(filename
, 0))
1644 * Country localization not available, look for generic localization...
1647 snprintf(filename
, sizeof(filename
), "%s/%.2s/cups_%.2s.po", cg
->localedir
,
1648 lang
->language
, lang
->language
);
1650 if (access(filename
, 0))
1653 * No generic localization, so use POSIX...
1656 DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename
,
1659 snprintf(filename
, sizeof(filename
), "%s/C/cups_C.po", cg
->localedir
);
1664 * Read the strings from the file...
1667 lang
->strings
= _cupsMessageLoad(filename
, 1);
1668 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1673 * 'cups_unquote()' - Unquote characters in strings...
1677 cups_unquote(char *d
, /* O - Unquoted string */
1678 const char *s
) /* I - Original string */
1691 *d
= *d
* 8 + *s
- '0';