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
) CF_RETURNS_RETAINED
;
144 # endif /* CUPS_BUNDLEDIR */
145 #endif /* __APPLE__ */
146 static cups_lang_t
*cups_cache_lookup(const char *name
, cups_encoding_t encoding
);
147 static int cups_message_compare(_cups_message_t
*m1
, _cups_message_t
*m2
);
148 static void cups_message_free(_cups_message_t
*m
);
149 static void cups_message_load(cups_lang_t
*lang
);
150 static void cups_message_puts(cups_file_t
*fp
, const char *s
);
151 static int cups_read_strings(cups_file_t
*fp
, int flags
, cups_array_t
*a
);
152 static void cups_unquote(char *d
, const char *s
);
157 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
161 const char * /* O - Language ID */
162 _cupsAppleLanguage(const char *locale
, /* I - Locale ID */
163 char *language
,/* I - Language ID buffer */
164 size_t langsize
) /* I - Size of language ID buffer */
166 int i
; /* Looping var */
167 CFStringRef localeid
, /* CF locale identifier */
168 langid
; /* CF language identifier */
172 * Copy the locale name and convert, as needed, to the Apple-specific
173 * locale identifier...
176 switch (strlen(locale
))
183 strlcpy(language
, "en", langsize
);
187 strlcpy(language
, locale
, langsize
);
191 strlcpy(language
, locale
, langsize
);
193 if (language
[2] == '-')
196 * Convert ll-cc to ll_CC...
200 language
[3] = (char)toupper(language
[3] & 255);
201 language
[4] = (char)toupper(language
[4] & 255);
207 i
< (int)(sizeof(apple_language_locale
) /
208 sizeof(apple_language_locale
[0]));
210 if (!strcmp(locale
, apple_language_locale
[i
].locale
))
212 strlcpy(language
, apple_language_locale
[i
].language
, sizeof(language
));
217 * Attempt to map the locale ID to a language ID...
220 if ((localeid
= CFStringCreateWithCString(kCFAllocatorDefault
, language
,
221 kCFStringEncodingASCII
)) != NULL
)
223 if ((langid
= CFLocaleCreateCanonicalLanguageIdentifierFromString(
224 kCFAllocatorDefault
, localeid
)) != NULL
)
226 CFStringGetCString(langid
, language
, (CFIndex
)langsize
, kCFStringEncodingASCII
);
234 * Return what we got...
242 * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID.
245 const char * /* O - Locale */
246 _cupsAppleLocale(CFStringRef languageName
, /* I - Apple language ID */
247 char *locale
, /* I - Buffer for locale */
248 size_t localesize
) /* I - Size of buffer */
250 int i
; /* Looping var */
251 CFStringRef localeName
; /* Locale as a CF string */
253 char temp
[1024]; /* Temporary string */
256 if (!CFStringGetCString(languageName
, temp
, (CFIndex
)sizeof(temp
), kCFStringEncodingASCII
))
259 DEBUG_printf(("_cupsAppleLocale(languageName=%p(%s), locale=%p, localsize=%d)", (void *)languageName
, temp
, (void *)locale
, (int)localesize
));
262 localeName
= CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault
, languageName
);
267 * Copy the locale name and tweak as needed...
270 if (!CFStringGetCString(localeName
, locale
, (CFIndex
)localesize
, kCFStringEncodingASCII
))
273 DEBUG_printf(("_cupsAppleLocale: locale=\"%s\"", locale
));
275 CFRelease(localeName
);
278 * Map new language identifiers to locales...
282 i
< (int)(sizeof(apple_language_locale
) /
283 sizeof(apple_language_locale
[0]));
286 size_t len
= strlen(apple_language_locale
[i
].language
);
288 if (!strcmp(locale
, apple_language_locale
[i
].language
) ||
289 (!strncmp(locale
, apple_language_locale
[i
].language
, len
) && (locale
[len
] == '_' || locale
[len
] == '-')))
291 DEBUG_printf(("_cupsAppleLocale: Updating locale to \"%s\".", apple_language_locale
[i
].locale
));
292 strlcpy(locale
, apple_language_locale
[i
].locale
, localesize
);
300 * Just try the Apple language name...
303 if (!CFStringGetCString(languageName
, locale
, (CFIndex
)localesize
, kCFStringEncodingASCII
))
309 DEBUG_puts("_cupsAppleLocale: Returning NULL.");
314 * Convert language subtag into region subtag...
317 if (locale
[2] == '-')
319 else if (locale
[3] == '-')
322 if (!strchr(locale
, '.'))
323 strlcat(locale
, ".UTF-8", localesize
);
325 DEBUG_printf(("_cupsAppleLocale: Returning \"%s\".", locale
));
329 #endif /* __APPLE__ */
333 * '_cupsEncodingName()' - Return the character encoding name string
334 * for the given encoding enumeration.
337 const char * /* O - Character encoding */
339 cups_encoding_t encoding
) /* I - Encoding value */
341 if (encoding
< CUPS_US_ASCII
||
342 encoding
>= (cups_encoding_t
)(sizeof(lang_encodings
) / sizeof(lang_encodings
[0])))
344 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
345 encoding
, lang_encodings
[0]));
346 return (lang_encodings
[0]);
350 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
351 encoding
, lang_encodings
[encoding
]));
352 return (lang_encodings
[encoding
]);
358 * 'cupsLangDefault()' - Return the default language.
361 cups_lang_t
* /* O - Language data */
362 cupsLangDefault(void)
364 return (cupsLangGet(NULL
));
369 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
370 * for the given language.
373 const char * /* O - Character encoding */
374 cupsLangEncoding(cups_lang_t
*lang
) /* I - Language data */
377 return ((char*)lang_encodings
[0]);
379 return ((char*)lang_encodings
[lang
->encoding
]);
384 * 'cupsLangFlush()' - Flush all language data out of the cache.
390 cups_lang_t
*lang
, /* Current language */
391 *next
; /* Next language */
395 * Free all languages in the cache...
398 _cupsMutexLock(&lang_mutex
);
400 for (lang
= lang_cache
; lang
!= NULL
; lang
= next
)
403 * Free all messages...
406 _cupsMessageFree(lang
->strings
);
409 * Then free the language structure itself...
418 _cupsMutexUnlock(&lang_mutex
);
423 * 'cupsLangFree()' - Free language data.
425 * This does not actually free anything; use @link cupsLangFlush@ for that.
429 cupsLangFree(cups_lang_t
*lang
) /* I - Language to free */
431 _cupsMutexLock(&lang_mutex
);
433 if (lang
!= NULL
&& lang
->used
> 0)
436 _cupsMutexUnlock(&lang_mutex
);
441 * 'cupsLangGet()' - Get a language.
444 cups_lang_t
* /* O - Language data */
445 cupsLangGet(const char *language
) /* I - Language or locale */
447 int i
; /* Looping var */
449 char locale
[255]; /* Copy of locale name */
450 #endif /* !__APPLE__ */
451 char langname
[16], /* Requested language name */
452 country
[16], /* Country code */
453 charset
[16], /* Character set */
454 *csptr
, /* Pointer to CODESET string */
455 *ptr
, /* Pointer into language/charset */
456 real
[48]; /* Real language name */
457 cups_encoding_t encoding
; /* Encoding to use */
458 cups_lang_t
*lang
; /* Current language... */
459 static const char * const locale_encodings
[] =
460 { /* Locale charset names */
461 "ASCII", "ISO88591", "ISO88592", "ISO88593",
462 "ISO88594", "ISO88595", "ISO88596", "ISO88597",
463 "ISO88598", "ISO88599", "ISO885910", "UTF8",
464 "ISO885913", "ISO885914", "ISO885915", "CP874",
465 "CP1250", "CP1251", "CP1252", "CP1253",
466 "CP1254", "CP1255", "CP1256", "CP1257",
467 "CP1258", "KOI8R", "KOI8U", "ISO885911",
468 "ISO885916", "MACROMAN", "", "",
479 "CP932", "CP936", "CP949", "CP950",
480 "CP1361", "", "", "",
497 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
502 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language
));
506 * Set the character set to UTF-8...
509 strlcpy(charset
, "UTF8", sizeof(charset
));
512 * Apple's setlocale doesn't give us the user's localization
513 * preference so we have to look it up this way...
518 if (!getenv("SOFTWARE") || (language
= getenv("LANG")) == NULL
)
519 language
= appleLangDefault();
521 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language
));
526 * Set the charset to "unknown"...
532 * Use setlocale() to determine the currently set locale, and then
533 * fallback to environment variables to avoid setting the locale,
534 * since setlocale() is not thread-safe!
540 * First see if the locale has been set; if it is still "C" or
541 * "POSIX", use the environment to get the default...
545 ptr
= setlocale(LC_MESSAGES
, NULL
);
547 ptr
= setlocale(LC_ALL
, NULL
);
548 # endif /* LC_MESSAGES */
550 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr
));
552 if (!ptr
|| !strcmp(ptr
, "C") || !strcmp(ptr
, "POSIX"))
555 * Get the character set from the LC_CTYPE locale setting...
558 if ((ptr
= getenv("LC_CTYPE")) == NULL
)
559 if ((ptr
= getenv("LC_ALL")) == NULL
)
560 if ((ptr
= getenv("LANG")) == NULL
)
563 if ((csptr
= strchr(ptr
, '.')) != NULL
)
566 * Extract the character set from the environment...
569 for (ptr
= charset
, csptr
++; *csptr
; csptr
++)
570 if (ptr
< (charset
+ sizeof(charset
) - 1) && _cups_isalnum(*csptr
))
577 * Get the locale for messages from the LC_MESSAGES locale setting...
580 if ((ptr
= getenv("LC_MESSAGES")) == NULL
)
581 if ((ptr
= getenv("LC_ALL")) == NULL
)
582 if ((ptr
= getenv("LANG")) == NULL
)
588 strlcpy(locale
, ptr
, sizeof(locale
));
592 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
595 if (!strncmp(locale
, "nb", 2))
598 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language
));
601 #endif /* __APPLE__ */
604 * If "language" is NULL at this point, then chances are we are using
605 * a language that is not installed for the base OS.
611 * Switch to the POSIX ("C") locale...
619 * On systems that support the nl_langinfo(CODESET) call, use
620 * this value as the character set...
623 if (!charset
[0] && (csptr
= nl_langinfo(CODESET
)) != NULL
)
626 * Copy all of the letters and numbers in the CODESET string...
629 for (ptr
= charset
; *csptr
; csptr
++)
630 if (_cups_isalnum(*csptr
) && ptr
< (charset
+ sizeof(charset
) - 1))
635 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
636 "nl_langinfo(CODESET)...", charset
));
641 * If we don't have a character set by now, default to UTF-8...
645 strlcpy(charset
, "UTF8", sizeof(charset
));
648 * Parse the language string passed in to a locale string. "C" is the
649 * standard POSIX locale and is copied unchanged. Otherwise the
650 * language string is converted from ll-cc[.charset] (language-country)
651 * to ll_CC[.CHARSET] to match the file naming convention used by all
652 * POSIX-compliant operating systems. Invalid language names are mapped
653 * to the POSIX locale.
658 if (language
== NULL
|| !language
[0] ||
659 !strcmp(language
, "POSIX"))
660 strlcpy(langname
, "C", sizeof(langname
));
664 * Copy the parts of the locale string over safely...
667 for (ptr
= langname
; *language
; language
++)
668 if (*language
== '_' || *language
== '-' || *language
== '.')
670 else if (ptr
< (langname
+ sizeof(langname
) - 1))
671 *ptr
++ = (char)tolower(*language
& 255);
675 if (*language
== '_' || *language
== '-')
678 * Copy the country code...
681 for (language
++, ptr
= country
; *language
; language
++)
682 if (*language
== '.')
684 else if (ptr
< (country
+ sizeof(country
) - 1))
685 *ptr
++ = (char)toupper(*language
& 255);
690 * Map Chinese region codes to legacy country codes.
693 if (!strcmp(language
, "zh") && !strcmp(country
, "HANS"))
694 strlcpy(country
, "CN", sizeof(country
));
695 if (!strcmp(language
, "zh") && !strcmp(country
, "HANT"))
696 strlcpy(country
, "TW", sizeof(country
));
699 if (*language
== '.' && !charset
[0])
702 * Copy the encoding...
705 for (language
++, ptr
= charset
; *language
; language
++)
706 if (_cups_isalnum(*language
) && ptr
< (charset
+ sizeof(charset
) - 1))
707 *ptr
++ = (char)toupper(*language
& 255);
713 * Force a POSIX locale for an invalid language name...
716 if (strlen(langname
) != 2 && strlen(langname
) != 3)
718 strlcpy(langname
, "C", sizeof(langname
));
724 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
725 langname
, country
, charset
));
728 * Figure out the desired encoding...
731 encoding
= CUPS_AUTO_ENCODING
;
736 i
< (int)(sizeof(locale_encodings
) / sizeof(locale_encodings
[0]));
738 if (!_cups_strcasecmp(charset
, locale_encodings
[i
]))
740 encoding
= (cups_encoding_t
)i
;
744 if (encoding
== CUPS_AUTO_ENCODING
)
747 * Map alternate names for various character sets...
750 if (!_cups_strcasecmp(charset
, "iso-2022-jp") ||
751 !_cups_strcasecmp(charset
, "sjis"))
752 encoding
= CUPS_WINDOWS_932
;
753 else if (!_cups_strcasecmp(charset
, "iso-2022-cn"))
754 encoding
= CUPS_WINDOWS_936
;
755 else if (!_cups_strcasecmp(charset
, "iso-2022-kr"))
756 encoding
= CUPS_WINDOWS_949
;
757 else if (!_cups_strcasecmp(charset
, "big5"))
758 encoding
= CUPS_WINDOWS_950
;
762 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding
,
763 encoding
== CUPS_AUTO_ENCODING
? "auto" :
764 lang_encodings
[encoding
]));
767 * See if we already have this language/country loaded...
771 snprintf(real
, sizeof(real
), "%s_%s", langname
, country
);
773 strlcpy(real
, langname
, sizeof(real
));
775 _cupsMutexLock(&lang_mutex
);
777 if ((lang
= cups_cache_lookup(real
, encoding
)) != NULL
)
779 _cupsMutexUnlock(&lang_mutex
);
781 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real
));
787 * See if there is a free language available; if so, use that
791 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
798 * Allocate memory for the language and add it to the cache.
801 if ((lang
= calloc(sizeof(cups_lang_t
), 1)) == NULL
)
803 _cupsMutexUnlock(&lang_mutex
);
808 lang
->next
= lang_cache
;
814 * Free all old strings as needed...
817 _cupsMessageFree(lang
->strings
);
818 lang
->strings
= NULL
;
822 * Then assign the language and encoding fields...
826 strlcpy(lang
->language
, real
, sizeof(lang
->language
));
828 if (encoding
!= CUPS_AUTO_ENCODING
)
829 lang
->encoding
= encoding
;
831 lang
->encoding
= CUPS_UTF8
;
837 _cupsMutexUnlock(&lang_mutex
);
844 * '_cupsLangString()' - Get a message string.
846 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
847 * convert the string to the language encoding.
850 const char * /* O - Localized message */
851 _cupsLangString(cups_lang_t
*lang
, /* I - Language */
852 const char *message
) /* I - Message */
854 const char *s
; /* Localized message */
857 DEBUG_printf(("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang
, message
));
860 * Range check input...
863 if (!lang
|| !message
|| !*message
)
866 _cupsMutexLock(&lang_mutex
);
869 * Load the message catalog if needed...
873 cups_message_load(lang
);
875 s
= _cupsMessageLookup(lang
->strings
, message
);
877 _cupsMutexUnlock(&lang_mutex
);
884 * '_cupsMessageFree()' - Free a messages array.
888 _cupsMessageFree(cups_array_t
*a
) /* I - Message array */
890 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
892 * Release the cups.strings dictionary as needed...
895 if (cupsArrayUserData(a
))
896 CFRelease((CFDictionaryRef
)cupsArrayUserData(a
));
897 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
908 * '_cupsMessageLoad()' - Load a .po or .strings file into a messages array.
911 cups_array_t
* /* O - New message array */
912 _cupsMessageLoad(const char *filename
, /* I - Message catalog to load */
913 int flags
) /* I - Load flags */
915 cups_file_t
*fp
; /* Message file */
916 cups_array_t
*a
; /* Message array */
917 _cups_message_t
*m
; /* Current message */
918 char s
[4096], /* String buffer */
919 *ptr
, /* Pointer into buffer */
920 *temp
; /* New string */
921 size_t length
, /* Length of combined strings */
922 ptrlen
; /* Length of string */
925 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename
));
928 * Create an array to hold the messages...
931 if ((a
= _cupsMessageNew(NULL
)) == NULL
)
933 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
938 * Open the message catalog file...
941 if ((fp
= cupsFileOpen(filename
, "r")) == NULL
)
943 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
948 if (flags
& _CUPS_MESSAGE_STRINGS
)
950 while (cups_read_strings(fp
, flags
, a
));
955 * Read messages from the catalog file until EOF...
957 * The format is the GNU gettext .po format, which is fairly simple:
960 * msgstr "localized text"
962 * The ID and localized text can span multiple lines using the form:
967 * "localized text spanning "
973 while (cupsFileGets(fp
, s
, sizeof(s
)) != NULL
)
976 * Skip blank and comment lines...
979 if (s
[0] == '#' || !s
[0])
983 * Strip the trailing quote...
986 if ((ptr
= strrchr(s
, '\"')) == NULL
)
992 * Find start of value...
995 if ((ptr
= strchr(s
, '\"')) == NULL
)
1001 * Unquote the text...
1004 if (flags
& _CUPS_MESSAGE_UNQUOTE
)
1005 cups_unquote(ptr
, ptr
);
1008 * Create or add to a message...
1011 if (!strncmp(s
, "msgid", 5))
1014 * Add previous message as needed...
1019 if (m
->str
&& m
->str
[0])
1026 * Translation is empty, don't add it... (STR #4033)
1037 * Create a new message with the given msgid string...
1040 if ((m
= (_cups_message_t
*)calloc(1, sizeof(_cups_message_t
))) == NULL
)
1043 if ((m
->msg
= strdup(ptr
)) == NULL
)
1050 else if (s
[0] == '\"' && m
)
1053 * Append to current string...
1056 length
= strlen(m
->str
? m
->str
: m
->msg
);
1057 ptrlen
= strlen(ptr
);
1059 if ((temp
= realloc(m
->str
? m
->str
: m
->msg
, length
+ ptrlen
+ 1)) == NULL
)
1072 * Copy the new portion to the end of the msgstr string - safe
1073 * to use memcpy because the buffer is allocated to the correct
1079 memcpy(m
->str
+ length
, ptr
, ptrlen
+ 1);
1084 * Copy the new portion to the end of the msgid string - safe
1085 * to use memcpy because the buffer is allocated to the correct
1091 memcpy(m
->msg
+ length
, ptr
, ptrlen
+ 1);
1094 else if (!strncmp(s
, "msgstr", 6) && m
)
1100 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)
1135 * Close the message catalog file and return the new array...
1140 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...", 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...
1165 key
.msg
= (char *)m
;
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
, kCFStringEncodingUTF8
);
1181 match
= calloc(1, sizeof(_cups_message_t
));
1182 match
->msg
= strdup(m
);
1183 cfstr
= cfm
? CFDictionaryGetValue(dict
, cfm
) : NULL
;
1187 char buffer
[1024]; /* Message buffer */
1189 CFStringGetCString(cfstr
, buffer
, sizeof(buffer
), kCFStringEncodingUTF8
);
1190 match
->str
= strdup(buffer
);
1192 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...", m
, buffer
));
1196 match
->str
= strdup(m
);
1198 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m
));
1201 cupsArrayAdd(a
, match
);
1206 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1208 if (match
&& match
->str
)
1209 return (match
->str
);
1216 * '_cupsMessageNew()' - Make a new message catalog array.
1219 cups_array_t
* /* O - Array */
1220 _cupsMessageNew(void *context
) /* I - User data */
1222 return (cupsArrayNew3((cups_array_func_t
)cups_message_compare
, context
,
1223 (cups_ahash_func_t
)NULL
, 0,
1224 (cups_acopy_func_t
)NULL
,
1225 (cups_afree_func_t
)cups_message_free
));
1230 * '_cupsMessageSave()' - Save a message catalog array.
1233 int /* O - 0 on success, -1 on failure */
1234 _cupsMessageSave(const char *filename
,/* I - Output filename */
1235 int flags
, /* I - Format flags */
1236 cups_array_t
*a
) /* I - Message array */
1238 cups_file_t
*fp
; /* Output file */
1239 _cups_message_t
*m
; /* Current message */
1243 * Output message catalog file...
1246 if ((fp
= cupsFileOpen(filename
, "w")) == NULL
)
1250 * Write each message...
1253 if (flags
& _CUPS_MESSAGE_STRINGS
)
1255 for (m
= (_cups_message_t
*)cupsArrayFirst(a
); m
; m
= (_cups_message_t
*)cupsArrayNext(a
))
1257 cupsFilePuts(fp
, "\"");
1258 cups_message_puts(fp
, m
->msg
);
1259 cupsFilePuts(fp
, "\" = \"");
1260 cups_message_puts(fp
, m
->str
);
1261 cupsFilePuts(fp
, "\";\n");
1266 for (m
= (_cups_message_t
*)cupsArrayFirst(a
); m
; m
= (_cups_message_t
*)cupsArrayNext(a
))
1268 cupsFilePuts(fp
, "msgid \"");
1269 cups_message_puts(fp
, m
->msg
);
1270 cupsFilePuts(fp
, "\"\nmsgstr \"");
1271 cups_message_puts(fp
, m
->str
);
1272 cupsFilePuts(fp
, "\"\n");
1276 return (cupsFileClose(fp
));
1282 * 'appleLangDefault()' - Get the default locale string.
1285 static const char * /* O - Locale string */
1286 appleLangDefault(void)
1288 CFBundleRef bundle
; /* Main bundle (if any) */
1289 CFArrayRef bundleList
; /* List of localizations in bundle */
1290 CFPropertyListRef localizationList
= NULL
;
1291 /* List of localization data */
1292 CFStringRef languageName
; /* Current name */
1293 char *lang
; /* LANG environment variable */
1294 _cups_globals_t
*cg
= _cupsGlobals();
1295 /* Pointer to library globals */
1298 DEBUG_puts("2appleLangDefault()");
1301 * Only do the lookup and translation the first time.
1304 if (!cg
->language
[0])
1306 if (getenv("SOFTWARE") != NULL
&& (lang
= getenv("LANG")) != NULL
)
1308 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang
));
1309 strlcpy(cg
->language
, lang
, sizeof(cg
->language
));
1310 return (cg
->language
);
1312 else if ((bundle
= CFBundleGetMainBundle()) != NULL
&&
1313 (bundleList
= CFBundleCopyBundleLocalizations(bundle
)) != NULL
)
1315 CFURLRef resources
= CFBundleCopyResourcesDirectoryURL(bundle
);
1317 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
1321 CFStringRef cfpath
= CFURLCopyPath(resources
);
1327 * See if we have an Info.plist file in the bundle...
1330 CFStringGetCString(cfpath
, path
, sizeof(path
), kCFStringEncodingUTF8
);
1331 DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path
));
1332 strlcat(path
, "Contents/Info.plist", sizeof(path
));
1334 if (!access(path
, R_OK
))
1335 localizationList
= CFBundleCopyPreferredLocalizationsFromArray(bundleList
);
1337 DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL...");
1342 CFRelease(resources
);
1345 DEBUG_puts("3appleLangDefault: No resource URL.");
1347 CFRelease(bundleList
);
1350 if (!localizationList
)
1352 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
1355 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1356 kCFPreferencesCurrentApplication
);
1359 if (localizationList
)
1362 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID())
1363 DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.",
1364 (int)CFArrayGetCount(localizationList
)));
1366 DEBUG_puts("3appleLangDefault: Got localizationList but not an array.");
1369 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID() &&
1370 CFArrayGetCount(localizationList
) > 0)
1372 languageName
= CFArrayGetValueAtIndex(localizationList
, 0);
1375 CFGetTypeID(languageName
) == CFStringGetTypeID())
1377 if (_cupsAppleLocale(languageName
, cg
->language
, sizeof(cg
->language
)))
1378 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
1381 DEBUG_puts("3appleLangDefault: Unable to get locale.");
1385 CFRelease(localizationList
);
1389 * If we didn't find the language, default to en_US...
1392 if (!cg
->language
[0])
1394 DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
1395 strlcpy(cg
->language
, "en_US.UTF-8", sizeof(cg
->language
));
1399 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg
->language
));
1402 * Return the cached locale...
1405 return (cg
->language
);
1409 # ifdef CUPS_BUNDLEDIR
1411 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1414 static cups_array_t
* /* O - Message catalog */
1415 appleMessageLoad(const char *locale
) /* I - Locale ID */
1417 char filename
[1024], /* Path to cups.strings file */
1418 applelang
[256], /* Apple language ID */
1419 baselang
[4]; /* Base language */
1420 CFURLRef url
; /* URL to cups.strings file */
1421 CFReadStreamRef stream
= NULL
; /* File stream */
1422 CFPropertyListRef plist
= NULL
; /* Localization file */
1424 const char *cups_strings
= getenv("CUPS_STRINGS");
1425 /* Test strings file */
1426 CFErrorRef error
= NULL
; /* Error when opening file */
1430 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale
));
1433 * Load the cups.strings file...
1439 DEBUG_puts("1appleMessageLoad: Using debug CUPS_STRINGS file.");
1440 strlcpy(filename
, cups_strings
, sizeof(filename
));
1445 snprintf(filename
, sizeof(filename
),
1446 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings",
1447 _cupsAppleLanguage(locale
, applelang
, sizeof(applelang
)));
1449 if (access(filename
, 0))
1452 * <rdar://problem/22086642>
1454 * Try with original locale string...
1457 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1458 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1461 if (access(filename
, 0))
1464 * <rdar://problem/25292403>
1466 * Try with just the language code...
1469 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1471 strlcpy(baselang
, locale
, sizeof(baselang
));
1472 if (baselang
[3] == '-' || baselang
[3] == '_')
1475 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", baselang
);
1478 if (access(filename
, 0))
1481 * Try alternate lproj directory names...
1484 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1486 if (!strncmp(locale
, "en", 2))
1488 else if (!strncmp(locale
, "nb", 2))
1490 else if (!strncmp(locale
, "nl", 2))
1492 else if (!strncmp(locale
, "fr", 2))
1494 else if (!strncmp(locale
, "de", 2))
1496 else if (!strncmp(locale
, "it", 2))
1498 else if (!strncmp(locale
, "ja", 2))
1499 locale
= "Japanese";
1500 else if (!strncmp(locale
, "es", 2))
1502 else if (!strcmp(locale
, "zh_HK") || !strncasecmp(locale
, "zh-Hant", 7) || !strncasecmp(locale
, "zh_Hant", 7))
1505 * <rdar://problem/22130168>
1506 * <rdar://problem/27245567>
1508 * Try zh_TW first, then zh... Sigh...
1511 if (!access(CUPS_BUNDLEDIR
"/Resources/zh_TW.lproj/cups.strings", 0))
1516 else if (strstr(locale
, "_") != NULL
|| strstr(locale
, "-") != NULL
)
1519 * Drop country code, just try language...
1522 strlcpy(baselang
, locale
, sizeof(baselang
));
1523 if (baselang
[2] == '-' || baselang
[2] == '_')
1529 snprintf(filename
, sizeof(filename
),
1530 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1533 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename
));
1535 url
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
,
1537 (CFIndex
)strlen(filename
), false);
1540 stream
= CFReadStreamCreateWithFile(kCFAllocatorDefault
, url
);
1544 * Read the property list containing the localization data.
1546 * NOTE: This code currently generates a clang "potential leak"
1547 * warning, but the object is released in _cupsMessageFree().
1550 CFReadStreamOpen(stream
);
1553 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1554 kCFPropertyListImmutable
, NULL
,
1558 CFStringRef msg
= CFErrorCopyDescription(error
);
1561 CFStringGetCString(msg
, filename
, sizeof(filename
),
1562 kCFStringEncodingUTF8
);
1563 DEBUG_printf(("1appleMessageLoad: %s", filename
));
1570 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1571 kCFPropertyListImmutable
, NULL
,
1575 if (plist
&& CFGetTypeID(plist
) != CFDictionaryGetTypeID())
1587 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url
, stream
,
1591 * Create and return an empty array to act as a cache for messages, passing the
1592 * plist as the user data.
1595 return (_cupsMessageNew((void *)plist
));
1597 # endif /* CUPS_BUNDLEDIR */
1598 #endif /* __APPLE__ */
1602 * 'cups_cache_lookup()' - Lookup a language in the cache...
1605 static cups_lang_t
* /* O - Language data or NULL */
1607 const char *name
, /* I - Name of locale */
1608 cups_encoding_t encoding
) /* I - Encoding of locale */
1610 cups_lang_t
*lang
; /* Current language */
1613 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name
,
1614 encoding
, encoding
== CUPS_AUTO_ENCODING
? "auto" :
1615 lang_encodings
[encoding
]));
1618 * Loop through the cache and return a match if found...
1621 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
1623 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1624 "encoding=%d(%s)", (void *)lang
, lang
->language
, lang
->encoding
,
1625 lang_encodings
[lang
->encoding
]));
1627 if (!strcmp(lang
->language
, name
) &&
1628 (encoding
== CUPS_AUTO_ENCODING
|| encoding
== lang
->encoding
))
1632 DEBUG_puts("8cups_cache_lookup: returning match!");
1638 DEBUG_puts("8cups_cache_lookup: returning NULL!");
1645 * 'cups_message_compare()' - Compare two messages.
1648 static int /* O - Result of comparison */
1649 cups_message_compare(
1650 _cups_message_t
*m1
, /* I - First message */
1651 _cups_message_t
*m2
) /* I - Second message */
1653 return (strcmp(m1
->msg
, m2
->msg
));
1658 * 'cups_message_free()' - Free a message.
1662 cups_message_free(_cups_message_t
*m
) /* I - Message */
1675 * 'cups_message_load()' - Load the message catalog for a language.
1679 cups_message_load(cups_lang_t
*lang
) /* I - Language */
1681 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1682 lang
->strings
= appleMessageLoad(lang
->language
);
1685 char filename
[1024]; /* Filename for language locale file */
1686 _cups_globals_t
*cg
= _cupsGlobals();
1687 /* Pointer to library globals */
1690 snprintf(filename
, sizeof(filename
), "%s/%s/cups_%s.po", cg
->localedir
,
1691 lang
->language
, lang
->language
);
1693 if (strchr(lang
->language
, '_') && access(filename
, 0))
1696 * Country localization not available, look for generic localization...
1699 snprintf(filename
, sizeof(filename
), "%s/%.2s/cups_%.2s.po", cg
->localedir
,
1700 lang
->language
, lang
->language
);
1702 if (access(filename
, 0))
1705 * No generic localization, so use POSIX...
1708 DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename
,
1711 snprintf(filename
, sizeof(filename
), "%s/C/cups_C.po", cg
->localedir
);
1716 * Read the strings from the file...
1719 lang
->strings
= _cupsMessageLoad(filename
, _CUPS_MESSAGE_UNQUOTE
);
1720 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1725 * 'cups_message_puts()' - Write a message string with quoting.
1729 cups_message_puts(cups_file_t
*fp
, /* I - File to write to */
1730 const char *s
) /* I - String to write */
1732 const char *start
, /* Start of substring */
1733 *ptr
; /* Pointer into string */
1736 for (start
= s
, ptr
= s
; *ptr
; ptr
++)
1738 if (strchr("\\\"\n\t", *ptr
))
1742 cupsFileWrite(fp
, start
, (size_t)(ptr
- start
));
1747 cupsFileWrite(fp
, "\\\\", 2);
1748 else if (*ptr
== '\"')
1749 cupsFileWrite(fp
, "\\\"", 2);
1750 else if (*ptr
== '\n')
1751 cupsFileWrite(fp
, "\\n", 2);
1752 else /* if (*ptr == '\t') */
1753 cupsFileWrite(fp
, "\\t", 2);
1758 cupsFileWrite(fp
, start
, (size_t)(ptr
- start
));
1763 * 'cups_read_strings()' - Read a pair of strings from a .strings file.
1766 static int /* O - 1 on success, 0 on failure */
1767 cups_read_strings(cups_file_t
*fp
, /* I - .strings file */
1768 int flags
, /* I - CUPS_MESSAGE_xxx flags */
1769 cups_array_t
*a
) /* I - Message catalog array */
1771 char buffer
[8192], /* Line buffer */
1772 *bufptr
, /* Pointer into buffer */
1773 *msg
, /* Pointer to start of message */
1774 *str
; /* Pointer to start of translation string */
1775 _cups_message_t
*m
; /* New message */
1778 while (cupsFileGets(fp
, buffer
, sizeof(buffer
)))
1781 * Skip any line (comments, blanks, etc.) that isn't:
1783 * "message" = "translation";
1786 for (bufptr
= buffer
; *bufptr
&& isspace(*bufptr
& 255); bufptr
++);
1788 if (*bufptr
!= '\"')
1792 * Find the end of the message...
1796 for (msg
= bufptr
; *bufptr
&& *bufptr
!= '\"'; bufptr
++)
1797 if (*bufptr
== '\\' && bufptr
[1])
1805 if (flags
& _CUPS_MESSAGE_UNQUOTE
)
1806 cups_unquote(msg
, msg
);
1809 * Find the start of the translation...
1812 while (*bufptr
&& isspace(*bufptr
& 255))
1819 while (*bufptr
&& isspace(*bufptr
& 255))
1822 if (*bufptr
!= '\"')
1826 * Find the end of the translation...
1830 for (str
= bufptr
; *bufptr
&& *bufptr
!= '\"'; bufptr
++)
1831 if (*bufptr
== '\\' && bufptr
[1])
1839 if (flags
& _CUPS_MESSAGE_UNQUOTE
)
1840 cups_unquote(str
, str
);
1843 * If we get this far we have a valid pair of strings, add them...
1846 if ((m
= malloc(sizeof(_cups_message_t
))) == NULL
)
1849 m
->msg
= strdup(msg
);
1850 m
->str
= strdup(str
);
1852 if (m
->msg
&& m
->str
)
1872 * No more strings...
1880 * 'cups_unquote()' - Unquote characters in strings...
1884 cups_unquote(char *d
, /* O - Unquoted string */
1885 const char *s
) /* I - Original string */
1898 *d
= *d
* 8 + *s
- '0';