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 #include "debug-internal.h"
16 #ifdef HAVE_LANGINFO_H
17 # include <langinfo.h>
18 #endif /* HAVE_LANGINFO_H */
24 #ifdef HAVE_COREFOUNDATION_H
25 # include <CoreFoundation/CoreFoundation.h>
26 #endif /* HAVE_COREFOUNDATION_H */
33 static _cups_mutex_t lang_mutex
= _CUPS_MUTEX_INITIALIZER
;
34 /* Mutex to control access to cache */
35 static cups_lang_t
*lang_cache
= NULL
;
36 /* Language string cache */
37 static const char * const lang_encodings
[] =
38 { /* Encoding strings */
39 "us-ascii", "iso-8859-1",
40 "iso-8859-2", "iso-8859-3",
41 "iso-8859-4", "iso-8859-5",
42 "iso-8859-6", "iso-8859-7",
43 "iso-8859-8", "iso-8859-9",
44 "iso-8859-10", "utf-8",
45 "iso-8859-13", "iso-8859-14",
46 "iso-8859-15", "cp874",
52 "koi8-u", "iso-8859-11",
100 "unknown", "unknown",
101 "unknown", "unknown",
102 "unknown", "unknown",
111 const char * const language
; /* Language ID */
112 const char * const locale
; /* Locale ID */
113 } _apple_language_locale_t
;
115 static const _apple_language_locale_t apple_language_locale
[] =
116 { /* Language to locale ID LUT */
120 { "zh-Hans", "zh_CN" },
121 { "zh_HANS", "zh_CN" },
122 { "zh-Hant", "zh_TW" },
123 { "zh_HANT", "zh_TW" },
124 { "zh-Hant_CN", "zh_TW" }
126 #endif /* __APPLE__ */
135 static const char *appleLangDefault(void);
136 # ifdef CUPS_BUNDLEDIR
137 # ifndef CF_RETURNS_RETAINED
138 # if __has_feature(attribute_cf_returns_retained)
139 # define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
141 # define CF_RETURNS_RETAINED
142 # endif /* __has_feature(attribute_cf_returns_retained) */
143 # endif /* !CF_RETURNED_RETAINED */
144 static cups_array_t
*appleMessageLoad(const char *locale
) CF_RETURNS_RETAINED
;
145 # endif /* CUPS_BUNDLEDIR */
146 #endif /* __APPLE__ */
147 static cups_lang_t
*cups_cache_lookup(const char *name
, cups_encoding_t encoding
);
148 static int cups_message_compare(_cups_message_t
*m1
, _cups_message_t
*m2
);
149 static void cups_message_free(_cups_message_t
*m
);
150 static void cups_message_load(cups_lang_t
*lang
);
151 static void cups_message_puts(cups_file_t
*fp
, const char *s
);
152 static int cups_read_strings(cups_file_t
*fp
, int flags
, cups_array_t
*a
);
153 static void cups_unquote(char *d
, const char *s
);
158 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
162 const char * /* O - Language ID */
163 _cupsAppleLanguage(const char *locale
, /* I - Locale ID */
164 char *language
,/* I - Language ID buffer */
165 size_t langsize
) /* I - Size of language ID buffer */
167 int i
; /* Looping var */
168 CFStringRef localeid
, /* CF locale identifier */
169 langid
; /* CF language identifier */
173 * Copy the locale name and convert, as needed, to the Apple-specific
174 * locale identifier...
177 switch (strlen(locale
))
184 strlcpy(language
, "en", langsize
);
188 strlcpy(language
, locale
, langsize
);
192 strlcpy(language
, locale
, langsize
);
194 if (language
[2] == '-')
197 * Convert ll-cc to ll_CC...
201 language
[3] = (char)toupper(language
[3] & 255);
202 language
[4] = (char)toupper(language
[4] & 255);
208 i
< (int)(sizeof(apple_language_locale
) /
209 sizeof(apple_language_locale
[0]));
211 if (!strcmp(locale
, apple_language_locale
[i
].locale
))
213 strlcpy(language
, apple_language_locale
[i
].language
, sizeof(language
));
218 * Attempt to map the locale ID to a language ID...
221 if ((localeid
= CFStringCreateWithCString(kCFAllocatorDefault
, language
,
222 kCFStringEncodingASCII
)) != NULL
)
224 if ((langid
= CFLocaleCreateCanonicalLanguageIdentifierFromString(
225 kCFAllocatorDefault
, localeid
)) != NULL
)
227 CFStringGetCString(langid
, language
, (CFIndex
)langsize
, kCFStringEncodingASCII
);
235 * Return what we got...
243 * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID.
246 const char * /* O - Locale */
247 _cupsAppleLocale(CFStringRef languageName
, /* I - Apple language ID */
248 char *locale
, /* I - Buffer for locale */
249 size_t localesize
) /* I - Size of buffer */
251 int i
; /* Looping var */
252 CFStringRef localeName
; /* Locale as a CF string */
254 char temp
[1024]; /* Temporary string */
257 if (!CFStringGetCString(languageName
, temp
, (CFIndex
)sizeof(temp
), kCFStringEncodingASCII
))
260 DEBUG_printf(("_cupsAppleLocale(languageName=%p(%s), locale=%p, localsize=%d)", (void *)languageName
, temp
, (void *)locale
, (int)localesize
));
263 localeName
= CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault
, languageName
);
268 * Copy the locale name and tweak as needed...
271 if (!CFStringGetCString(localeName
, locale
, (CFIndex
)localesize
, kCFStringEncodingASCII
))
274 DEBUG_printf(("_cupsAppleLocale: locale=\"%s\"", locale
));
276 CFRelease(localeName
);
279 * Map new language identifiers to locales...
283 i
< (int)(sizeof(apple_language_locale
) /
284 sizeof(apple_language_locale
[0]));
287 size_t len
= strlen(apple_language_locale
[i
].language
);
289 if (!strcmp(locale
, apple_language_locale
[i
].language
) ||
290 (!strncmp(locale
, apple_language_locale
[i
].language
, len
) && (locale
[len
] == '_' || locale
[len
] == '-')))
292 DEBUG_printf(("_cupsAppleLocale: Updating locale to \"%s\".", apple_language_locale
[i
].locale
));
293 strlcpy(locale
, apple_language_locale
[i
].locale
, localesize
);
301 * Just try the Apple language name...
304 if (!CFStringGetCString(languageName
, locale
, (CFIndex
)localesize
, kCFStringEncodingASCII
))
310 DEBUG_puts("_cupsAppleLocale: Returning NULL.");
315 * Convert language subtag into region subtag...
318 if (locale
[2] == '-')
320 else if (locale
[3] == '-')
323 if (!strchr(locale
, '.'))
324 strlcat(locale
, ".UTF-8", localesize
);
326 DEBUG_printf(("_cupsAppleLocale: Returning \"%s\".", locale
));
330 #endif /* __APPLE__ */
334 * '_cupsEncodingName()' - Return the character encoding name string
335 * for the given encoding enumeration.
338 const char * /* O - Character encoding */
340 cups_encoding_t encoding
) /* I - Encoding value */
342 if (encoding
< CUPS_US_ASCII
||
343 encoding
>= (cups_encoding_t
)(sizeof(lang_encodings
) / sizeof(lang_encodings
[0])))
345 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
346 encoding
, lang_encodings
[0]));
347 return (lang_encodings
[0]);
351 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
352 encoding
, lang_encodings
[encoding
]));
353 return (lang_encodings
[encoding
]);
359 * 'cupsLangDefault()' - Return the default language.
362 cups_lang_t
* /* O - Language data */
363 cupsLangDefault(void)
365 return (cupsLangGet(NULL
));
370 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
371 * for the given language.
374 const char * /* O - Character encoding */
375 cupsLangEncoding(cups_lang_t
*lang
) /* I - Language data */
378 return ((char*)lang_encodings
[0]);
380 return ((char*)lang_encodings
[lang
->encoding
]);
385 * 'cupsLangFlush()' - Flush all language data out of the cache.
391 cups_lang_t
*lang
, /* Current language */
392 *next
; /* Next language */
396 * Free all languages in the cache...
399 _cupsMutexLock(&lang_mutex
);
401 for (lang
= lang_cache
; lang
!= NULL
; lang
= next
)
404 * Free all messages...
407 _cupsMessageFree(lang
->strings
);
410 * Then free the language structure itself...
419 _cupsMutexUnlock(&lang_mutex
);
424 * 'cupsLangFree()' - Free language data.
426 * This does not actually free anything; use @link cupsLangFlush@ for that.
430 cupsLangFree(cups_lang_t
*lang
) /* I - Language to free */
432 _cupsMutexLock(&lang_mutex
);
434 if (lang
!= NULL
&& lang
->used
> 0)
437 _cupsMutexUnlock(&lang_mutex
);
442 * 'cupsLangGet()' - Get a language.
445 cups_lang_t
* /* O - Language data */
446 cupsLangGet(const char *language
) /* I - Language or locale */
448 int i
; /* Looping var */
450 char locale
[255]; /* Copy of locale name */
451 #endif /* !__APPLE__ */
452 char langname
[16], /* Requested language name */
453 country
[16], /* Country code */
454 charset
[16], /* Character set */
455 *csptr
, /* Pointer to CODESET string */
456 *ptr
, /* Pointer into language/charset */
457 real
[48]; /* Real language name */
458 cups_encoding_t encoding
; /* Encoding to use */
459 cups_lang_t
*lang
; /* Current language... */
460 static const char * const locale_encodings
[] =
461 { /* Locale charset names */
462 "ASCII", "ISO88591", "ISO88592", "ISO88593",
463 "ISO88594", "ISO88595", "ISO88596", "ISO88597",
464 "ISO88598", "ISO88599", "ISO885910", "UTF8",
465 "ISO885913", "ISO885914", "ISO885915", "CP874",
466 "CP1250", "CP1251", "CP1252", "CP1253",
467 "CP1254", "CP1255", "CP1256", "CP1257",
468 "CP1258", "KOI8R", "KOI8U", "ISO885911",
469 "ISO885916", "MACROMAN", "", "",
480 "CP932", "CP936", "CP949", "CP950",
481 "CP1361", "", "", "",
498 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
503 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language
));
507 * Set the character set to UTF-8...
510 strlcpy(charset
, "UTF8", sizeof(charset
));
513 * Apple's setlocale doesn't give us the user's localization
514 * preference so we have to look it up this way...
519 if (!getenv("SOFTWARE") || (language
= getenv("LANG")) == NULL
)
520 language
= appleLangDefault();
522 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language
));
527 * Set the charset to "unknown"...
533 * Use setlocale() to determine the currently set locale, and then
534 * fallback to environment variables to avoid setting the locale,
535 * since setlocale() is not thread-safe!
541 * First see if the locale has been set; if it is still "C" or
542 * "POSIX", use the environment to get the default...
546 ptr
= setlocale(LC_MESSAGES
, NULL
);
548 ptr
= setlocale(LC_ALL
, NULL
);
549 # endif /* LC_MESSAGES */
551 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr
));
553 if (!ptr
|| !strcmp(ptr
, "C") || !strcmp(ptr
, "POSIX"))
556 * Get the character set from the LC_CTYPE locale setting...
559 if ((ptr
= getenv("LC_CTYPE")) == NULL
)
560 if ((ptr
= getenv("LC_ALL")) == NULL
)
561 if ((ptr
= getenv("LANG")) == NULL
)
564 if ((csptr
= strchr(ptr
, '.')) != NULL
)
567 * Extract the character set from the environment...
570 for (ptr
= charset
, csptr
++; *csptr
; csptr
++)
571 if (ptr
< (charset
+ sizeof(charset
) - 1) && _cups_isalnum(*csptr
))
578 * Get the locale for messages from the LC_MESSAGES locale setting...
581 if ((ptr
= getenv("LC_MESSAGES")) == NULL
)
582 if ((ptr
= getenv("LC_ALL")) == NULL
)
583 if ((ptr
= getenv("LANG")) == NULL
)
589 strlcpy(locale
, ptr
, sizeof(locale
));
593 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
596 if (!strncmp(locale
, "nb", 2))
599 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language
));
602 #endif /* __APPLE__ */
605 * If "language" is NULL at this point, then chances are we are using
606 * a language that is not installed for the base OS.
612 * Switch to the POSIX ("C") locale...
620 * On systems that support the nl_langinfo(CODESET) call, use
621 * this value as the character set...
624 if (!charset
[0] && (csptr
= nl_langinfo(CODESET
)) != NULL
)
627 * Copy all of the letters and numbers in the CODESET string...
630 for (ptr
= charset
; *csptr
; csptr
++)
631 if (_cups_isalnum(*csptr
) && ptr
< (charset
+ sizeof(charset
) - 1))
636 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
637 "nl_langinfo(CODESET)...", charset
));
642 * If we don't have a character set by now, default to UTF-8...
646 strlcpy(charset
, "UTF8", sizeof(charset
));
649 * Parse the language string passed in to a locale string. "C" is the
650 * standard POSIX locale and is copied unchanged. Otherwise the
651 * language string is converted from ll-cc[.charset] (language-country)
652 * to ll_CC[.CHARSET] to match the file naming convention used by all
653 * POSIX-compliant operating systems. Invalid language names are mapped
654 * to the POSIX locale.
659 if (language
== NULL
|| !language
[0] ||
660 !strcmp(language
, "POSIX"))
661 strlcpy(langname
, "C", sizeof(langname
));
665 * Copy the parts of the locale string over safely...
668 for (ptr
= langname
; *language
; language
++)
669 if (*language
== '_' || *language
== '-' || *language
== '.')
671 else if (ptr
< (langname
+ sizeof(langname
) - 1))
672 *ptr
++ = (char)tolower(*language
& 255);
676 if (*language
== '_' || *language
== '-')
679 * Copy the country code...
682 for (language
++, ptr
= country
; *language
; language
++)
683 if (*language
== '.')
685 else if (ptr
< (country
+ sizeof(country
) - 1))
686 *ptr
++ = (char)toupper(*language
& 255);
691 * Map Chinese region codes to legacy country codes.
694 if (!strcmp(language
, "zh") && !strcmp(country
, "HANS"))
695 strlcpy(country
, "CN", sizeof(country
));
696 if (!strcmp(language
, "zh") && !strcmp(country
, "HANT"))
697 strlcpy(country
, "TW", sizeof(country
));
700 if (*language
== '.' && !charset
[0])
703 * Copy the encoding...
706 for (language
++, ptr
= charset
; *language
; language
++)
707 if (_cups_isalnum(*language
) && ptr
< (charset
+ sizeof(charset
) - 1))
708 *ptr
++ = (char)toupper(*language
& 255);
714 * Force a POSIX locale for an invalid language name...
717 if (strlen(langname
) != 2 && strlen(langname
) != 3)
719 strlcpy(langname
, "C", sizeof(langname
));
725 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
726 langname
, country
, charset
));
729 * Figure out the desired encoding...
732 encoding
= CUPS_AUTO_ENCODING
;
737 i
< (int)(sizeof(locale_encodings
) / sizeof(locale_encodings
[0]));
739 if (!_cups_strcasecmp(charset
, locale_encodings
[i
]))
741 encoding
= (cups_encoding_t
)i
;
745 if (encoding
== CUPS_AUTO_ENCODING
)
748 * Map alternate names for various character sets...
751 if (!_cups_strcasecmp(charset
, "iso-2022-jp") ||
752 !_cups_strcasecmp(charset
, "sjis"))
753 encoding
= CUPS_WINDOWS_932
;
754 else if (!_cups_strcasecmp(charset
, "iso-2022-cn"))
755 encoding
= CUPS_WINDOWS_936
;
756 else if (!_cups_strcasecmp(charset
, "iso-2022-kr"))
757 encoding
= CUPS_WINDOWS_949
;
758 else if (!_cups_strcasecmp(charset
, "big5"))
759 encoding
= CUPS_WINDOWS_950
;
763 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding
,
764 encoding
== CUPS_AUTO_ENCODING
? "auto" :
765 lang_encodings
[encoding
]));
768 * See if we already have this language/country loaded...
772 snprintf(real
, sizeof(real
), "%s_%s", langname
, country
);
774 strlcpy(real
, langname
, sizeof(real
));
776 _cupsMutexLock(&lang_mutex
);
778 if ((lang
= cups_cache_lookup(real
, encoding
)) != NULL
)
780 _cupsMutexUnlock(&lang_mutex
);
782 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real
));
788 * See if there is a free language available; if so, use that
792 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
799 * Allocate memory for the language and add it to the cache.
802 if ((lang
= calloc(sizeof(cups_lang_t
), 1)) == NULL
)
804 _cupsMutexUnlock(&lang_mutex
);
809 lang
->next
= lang_cache
;
815 * Free all old strings as needed...
818 _cupsMessageFree(lang
->strings
);
819 lang
->strings
= NULL
;
823 * Then assign the language and encoding fields...
827 strlcpy(lang
->language
, real
, sizeof(lang
->language
));
829 if (encoding
!= CUPS_AUTO_ENCODING
)
830 lang
->encoding
= encoding
;
832 lang
->encoding
= CUPS_UTF8
;
838 _cupsMutexUnlock(&lang_mutex
);
845 * '_cupsLangString()' - Get a message string.
847 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
848 * convert the string to the language encoding.
851 const char * /* O - Localized message */
852 _cupsLangString(cups_lang_t
*lang
, /* I - Language */
853 const char *message
) /* I - Message */
855 const char *s
; /* Localized message */
858 DEBUG_printf(("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang
, message
));
861 * Range check input...
864 if (!lang
|| !message
|| !*message
)
867 _cupsMutexLock(&lang_mutex
);
870 * Load the message catalog if needed...
874 cups_message_load(lang
);
876 s
= _cupsMessageLookup(lang
->strings
, message
);
878 _cupsMutexUnlock(&lang_mutex
);
885 * '_cupsMessageFree()' - Free a messages array.
889 _cupsMessageFree(cups_array_t
*a
) /* I - Message array */
891 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
893 * Release the cups.strings dictionary as needed...
896 if (cupsArrayUserData(a
))
897 CFRelease((CFDictionaryRef
)cupsArrayUserData(a
));
898 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
909 * '_cupsMessageLoad()' - Load a .po or .strings file into a messages array.
912 cups_array_t
* /* O - New message array */
913 _cupsMessageLoad(const char *filename
, /* I - Message catalog to load */
914 int flags
) /* I - Load flags */
916 cups_file_t
*fp
; /* Message file */
917 cups_array_t
*a
; /* Message array */
918 _cups_message_t
*m
; /* Current message */
919 char s
[4096], /* String buffer */
920 *ptr
, /* Pointer into buffer */
921 *temp
; /* New string */
922 size_t length
, /* Length of combined strings */
923 ptrlen
; /* Length of string */
926 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename
));
929 * Create an array to hold the messages...
932 if ((a
= _cupsMessageNew(NULL
)) == NULL
)
934 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
939 * Open the message catalog file...
942 if ((fp
= cupsFileOpen(filename
, "r")) == NULL
)
944 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
949 if (flags
& _CUPS_MESSAGE_STRINGS
)
951 while (cups_read_strings(fp
, flags
, a
));
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...
1005 if (flags
& _CUPS_MESSAGE_UNQUOTE
)
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] || (flags
& _CUPS_MESSAGE_EMPTY
)))
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
)
1044 if ((m
->msg
= strdup(ptr
)) == NULL
)
1051 else if (s
[0] == '\"' && m
)
1054 * Append to current string...
1057 length
= strlen(m
->str
? m
->str
: m
->msg
);
1058 ptrlen
= strlen(ptr
);
1060 if ((temp
= realloc(m
->str
? m
->str
: m
->msg
, length
+ ptrlen
+ 1)) == NULL
)
1073 * Copy the new portion to the end of the msgstr string - safe
1074 * to use memcpy because the buffer is allocated to the correct
1080 memcpy(m
->str
+ length
, ptr
, ptrlen
+ 1);
1085 * Copy the new portion to the end of the msgid string - safe
1086 * to use memcpy because the buffer is allocated to the correct
1092 memcpy(m
->msg
+ length
, ptr
, ptrlen
+ 1);
1095 else if (!strncmp(s
, "msgstr", 6) && m
)
1101 if ((m
->str
= strdup(ptr
)) == NULL
)
1112 * Add the last message string to the array as needed...
1117 if (m
->str
&& (m
->str
[0] || (flags
& _CUPS_MESSAGE_EMPTY
)))
1124 * Translation is empty, don't add it... (STR #4033)
1136 * Close the message catalog file and return the new array...
1141 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...", cupsArrayCount(a
)));
1148 * '_cupsMessageLookup()' - Lookup a message string.
1151 const char * /* O - Localized message */
1152 _cupsMessageLookup(cups_array_t
*a
, /* I - Message array */
1153 const char *m
) /* I - Message */
1155 _cups_message_t key
, /* Search key */
1156 *match
; /* Matching message */
1159 DEBUG_printf(("_cupsMessageLookup(a=%p, m=\"%s\")", (void *)a
, m
));
1162 * Lookup the message string; if it doesn't exist in the catalog,
1163 * then return the message that was passed to us...
1166 key
.msg
= (char *)m
;
1167 match
= (_cups_message_t
*)cupsArrayFind(a
, &key
);
1169 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1170 if (!match
&& cupsArrayUserData(a
))
1173 * Try looking the string up in the cups.strings dictionary...
1176 CFDictionaryRef dict
; /* cups.strings dictionary */
1177 CFStringRef cfm
, /* Message as a CF string */
1178 cfstr
; /* Localized text as a CF string */
1180 dict
= (CFDictionaryRef
)cupsArrayUserData(a
);
1181 cfm
= CFStringCreateWithCString(kCFAllocatorDefault
, m
, kCFStringEncodingUTF8
);
1182 match
= calloc(1, sizeof(_cups_message_t
));
1183 match
->msg
= strdup(m
);
1184 cfstr
= cfm
? CFDictionaryGetValue(dict
, cfm
) : NULL
;
1188 char buffer
[1024]; /* Message buffer */
1190 CFStringGetCString(cfstr
, buffer
, sizeof(buffer
), kCFStringEncodingUTF8
);
1191 match
->str
= strdup(buffer
);
1193 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...", m
, buffer
));
1197 match
->str
= strdup(m
);
1199 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m
));
1202 cupsArrayAdd(a
, match
);
1207 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1209 if (match
&& match
->str
)
1210 return (match
->str
);
1217 * '_cupsMessageNew()' - Make a new message catalog array.
1220 cups_array_t
* /* O - Array */
1221 _cupsMessageNew(void *context
) /* I - User data */
1223 return (cupsArrayNew3((cups_array_func_t
)cups_message_compare
, context
,
1224 (cups_ahash_func_t
)NULL
, 0,
1225 (cups_acopy_func_t
)NULL
,
1226 (cups_afree_func_t
)cups_message_free
));
1231 * '_cupsMessageSave()' - Save a message catalog array.
1234 int /* O - 0 on success, -1 on failure */
1235 _cupsMessageSave(const char *filename
,/* I - Output filename */
1236 int flags
, /* I - Format flags */
1237 cups_array_t
*a
) /* I - Message array */
1239 cups_file_t
*fp
; /* Output file */
1240 _cups_message_t
*m
; /* Current message */
1244 * Output message catalog file...
1247 if ((fp
= cupsFileOpen(filename
, "w")) == NULL
)
1251 * Write each message...
1254 if (flags
& _CUPS_MESSAGE_STRINGS
)
1256 for (m
= (_cups_message_t
*)cupsArrayFirst(a
); m
; m
= (_cups_message_t
*)cupsArrayNext(a
))
1258 cupsFilePuts(fp
, "\"");
1259 cups_message_puts(fp
, m
->msg
);
1260 cupsFilePuts(fp
, "\" = \"");
1261 cups_message_puts(fp
, m
->str
);
1262 cupsFilePuts(fp
, "\";\n");
1267 for (m
= (_cups_message_t
*)cupsArrayFirst(a
); m
; m
= (_cups_message_t
*)cupsArrayNext(a
))
1269 cupsFilePuts(fp
, "msgid \"");
1270 cups_message_puts(fp
, m
->msg
);
1271 cupsFilePuts(fp
, "\"\nmsgstr \"");
1272 cups_message_puts(fp
, m
->str
);
1273 cupsFilePuts(fp
, "\"\n");
1277 return (cupsFileClose(fp
));
1283 * 'appleLangDefault()' - Get the default locale string.
1286 static const char * /* O - Locale string */
1287 appleLangDefault(void)
1289 CFBundleRef bundle
; /* Main bundle (if any) */
1290 CFArrayRef bundleList
; /* List of localizations in bundle */
1291 CFPropertyListRef localizationList
= NULL
;
1292 /* List of localization data */
1293 CFStringRef languageName
; /* Current name */
1294 char *lang
; /* LANG environment variable */
1295 _cups_globals_t
*cg
= _cupsGlobals();
1296 /* Pointer to library globals */
1299 DEBUG_puts("2appleLangDefault()");
1302 * Only do the lookup and translation the first time.
1305 if (!cg
->language
[0])
1307 if (getenv("SOFTWARE") != NULL
&& (lang
= getenv("LANG")) != NULL
)
1309 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang
));
1310 strlcpy(cg
->language
, lang
, sizeof(cg
->language
));
1311 return (cg
->language
);
1313 else if ((bundle
= CFBundleGetMainBundle()) != NULL
&&
1314 (bundleList
= CFBundleCopyBundleLocalizations(bundle
)) != NULL
)
1316 CFURLRef resources
= CFBundleCopyResourcesDirectoryURL(bundle
);
1318 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
1322 CFStringRef cfpath
= CFURLCopyPath(resources
);
1328 * See if we have an Info.plist file in the bundle...
1331 CFStringGetCString(cfpath
, path
, sizeof(path
), kCFStringEncodingUTF8
);
1332 DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path
));
1333 strlcat(path
, "Contents/Info.plist", sizeof(path
));
1335 if (!access(path
, R_OK
))
1336 localizationList
= CFBundleCopyPreferredLocalizationsFromArray(bundleList
);
1338 DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL...");
1343 CFRelease(resources
);
1346 DEBUG_puts("3appleLangDefault: No resource URL.");
1348 CFRelease(bundleList
);
1351 if (!localizationList
)
1353 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
1356 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1357 kCFPreferencesCurrentApplication
);
1360 if (localizationList
)
1363 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID())
1364 DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.",
1365 (int)CFArrayGetCount(localizationList
)));
1367 DEBUG_puts("3appleLangDefault: Got localizationList but not an array.");
1370 if (CFGetTypeID(localizationList
) == CFArrayGetTypeID() &&
1371 CFArrayGetCount(localizationList
) > 0)
1373 languageName
= CFArrayGetValueAtIndex(localizationList
, 0);
1376 CFGetTypeID(languageName
) == CFStringGetTypeID())
1378 if (_cupsAppleLocale(languageName
, cg
->language
, sizeof(cg
->language
)))
1379 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
1382 DEBUG_puts("3appleLangDefault: Unable to get locale.");
1386 CFRelease(localizationList
);
1390 * If we didn't find the language, default to en_US...
1393 if (!cg
->language
[0])
1395 DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
1396 strlcpy(cg
->language
, "en_US.UTF-8", sizeof(cg
->language
));
1400 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg
->language
));
1403 * Return the cached locale...
1406 return (cg
->language
);
1410 # ifdef CUPS_BUNDLEDIR
1412 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1415 static cups_array_t
* /* O - Message catalog */
1416 appleMessageLoad(const char *locale
) /* I - Locale ID */
1418 char filename
[1024], /* Path to cups.strings file */
1419 applelang
[256], /* Apple language ID */
1420 baselang
[4]; /* Base language */
1421 CFURLRef url
; /* URL to cups.strings file */
1422 CFReadStreamRef stream
= NULL
; /* File stream */
1423 CFPropertyListRef plist
= NULL
; /* Localization file */
1425 const char *cups_strings
= getenv("CUPS_STRINGS");
1426 /* Test strings file */
1427 CFErrorRef error
= NULL
; /* Error when opening file */
1431 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale
));
1434 * Load the cups.strings file...
1440 DEBUG_puts("1appleMessageLoad: Using debug CUPS_STRINGS file.");
1441 strlcpy(filename
, cups_strings
, sizeof(filename
));
1446 snprintf(filename
, sizeof(filename
),
1447 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings",
1448 _cupsAppleLanguage(locale
, applelang
, sizeof(applelang
)));
1450 if (access(filename
, 0))
1453 * <rdar://problem/22086642>
1455 * Try with original locale string...
1458 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1459 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1462 if (access(filename
, 0))
1465 * <rdar://problem/25292403>
1467 * Try with just the language code...
1470 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1472 strlcpy(baselang
, locale
, sizeof(baselang
));
1473 if (baselang
[3] == '-' || baselang
[3] == '_')
1476 snprintf(filename
, sizeof(filename
), CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", baselang
);
1479 if (access(filename
, 0))
1482 * Try alternate lproj directory names...
1485 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename
, strerror(errno
)));
1487 if (!strncmp(locale
, "en", 2))
1489 else if (!strncmp(locale
, "nb", 2))
1491 else if (!strncmp(locale
, "nl", 2))
1493 else if (!strncmp(locale
, "fr", 2))
1495 else if (!strncmp(locale
, "de", 2))
1497 else if (!strncmp(locale
, "it", 2))
1499 else if (!strncmp(locale
, "ja", 2))
1500 locale
= "Japanese";
1501 else if (!strncmp(locale
, "es", 2))
1503 else if (!strcmp(locale
, "zh_HK") || !strncasecmp(locale
, "zh-Hant", 7) || !strncasecmp(locale
, "zh_Hant", 7))
1506 * <rdar://problem/22130168>
1507 * <rdar://problem/27245567>
1509 * Try zh_TW first, then zh... Sigh...
1512 if (!access(CUPS_BUNDLEDIR
"/Resources/zh_TW.lproj/cups.strings", 0))
1517 else if (strstr(locale
, "_") != NULL
|| strstr(locale
, "-") != NULL
)
1520 * Drop country code, just try language...
1523 strlcpy(baselang
, locale
, sizeof(baselang
));
1524 if (baselang
[2] == '-' || baselang
[2] == '_')
1530 snprintf(filename
, sizeof(filename
),
1531 CUPS_BUNDLEDIR
"/Resources/%s.lproj/cups.strings", locale
);
1534 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename
));
1536 url
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
,
1538 (CFIndex
)strlen(filename
), false);
1541 stream
= CFReadStreamCreateWithFile(kCFAllocatorDefault
, url
);
1545 * Read the property list containing the localization data.
1547 * NOTE: This code currently generates a clang "potential leak"
1548 * warning, but the object is released in _cupsMessageFree().
1551 CFReadStreamOpen(stream
);
1554 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1555 kCFPropertyListImmutable
, NULL
,
1559 CFStringRef msg
= CFErrorCopyDescription(error
);
1562 CFStringGetCString(msg
, filename
, sizeof(filename
),
1563 kCFStringEncodingUTF8
);
1564 DEBUG_printf(("1appleMessageLoad: %s", filename
));
1571 plist
= CFPropertyListCreateWithStream(kCFAllocatorDefault
, stream
, 0,
1572 kCFPropertyListImmutable
, NULL
,
1576 if (plist
&& CFGetTypeID(plist
) != CFDictionaryGetTypeID())
1588 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url
, stream
,
1592 * Create and return an empty array to act as a cache for messages, passing the
1593 * plist as the user data.
1596 return (_cupsMessageNew((void *)plist
));
1598 # endif /* CUPS_BUNDLEDIR */
1599 #endif /* __APPLE__ */
1603 * 'cups_cache_lookup()' - Lookup a language in the cache...
1606 static cups_lang_t
* /* O - Language data or NULL */
1608 const char *name
, /* I - Name of locale */
1609 cups_encoding_t encoding
) /* I - Encoding of locale */
1611 cups_lang_t
*lang
; /* Current language */
1614 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name
,
1615 encoding
, encoding
== CUPS_AUTO_ENCODING
? "auto" :
1616 lang_encodings
[encoding
]));
1619 * Loop through the cache and return a match if found...
1622 for (lang
= lang_cache
; lang
!= NULL
; lang
= lang
->next
)
1624 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1625 "encoding=%d(%s)", (void *)lang
, lang
->language
, lang
->encoding
,
1626 lang_encodings
[lang
->encoding
]));
1628 if (!strcmp(lang
->language
, name
) &&
1629 (encoding
== CUPS_AUTO_ENCODING
|| encoding
== lang
->encoding
))
1633 DEBUG_puts("8cups_cache_lookup: returning match!");
1639 DEBUG_puts("8cups_cache_lookup: returning NULL!");
1646 * 'cups_message_compare()' - Compare two messages.
1649 static int /* O - Result of comparison */
1650 cups_message_compare(
1651 _cups_message_t
*m1
, /* I - First message */
1652 _cups_message_t
*m2
) /* I - Second message */
1654 return (strcmp(m1
->msg
, m2
->msg
));
1659 * 'cups_message_free()' - Free a message.
1663 cups_message_free(_cups_message_t
*m
) /* I - Message */
1676 * 'cups_message_load()' - Load the message catalog for a language.
1680 cups_message_load(cups_lang_t
*lang
) /* I - Language */
1682 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1683 lang
->strings
= appleMessageLoad(lang
->language
);
1686 char filename
[1024]; /* Filename for language locale file */
1687 _cups_globals_t
*cg
= _cupsGlobals();
1688 /* Pointer to library globals */
1691 snprintf(filename
, sizeof(filename
), "%s/%s/cups_%s.po", cg
->localedir
,
1692 lang
->language
, lang
->language
);
1694 if (strchr(lang
->language
, '_') && access(filename
, 0))
1697 * Country localization not available, look for generic localization...
1700 snprintf(filename
, sizeof(filename
), "%s/%.2s/cups_%.2s.po", cg
->localedir
,
1701 lang
->language
, lang
->language
);
1703 if (access(filename
, 0))
1706 * No generic localization, so use POSIX...
1709 DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename
,
1712 snprintf(filename
, sizeof(filename
), "%s/C/cups_C.po", cg
->localedir
);
1717 * Read the strings from the file...
1720 lang
->strings
= _cupsMessageLoad(filename
, _CUPS_MESSAGE_UNQUOTE
);
1721 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1726 * 'cups_message_puts()' - Write a message string with quoting.
1730 cups_message_puts(cups_file_t
*fp
, /* I - File to write to */
1731 const char *s
) /* I - String to write */
1733 const char *start
, /* Start of substring */
1734 *ptr
; /* Pointer into string */
1737 for (start
= s
, ptr
= s
; *ptr
; ptr
++)
1739 if (strchr("\\\"\n\t", *ptr
))
1743 cupsFileWrite(fp
, start
, (size_t)(ptr
- start
));
1748 cupsFileWrite(fp
, "\\\\", 2);
1749 else if (*ptr
== '\"')
1750 cupsFileWrite(fp
, "\\\"", 2);
1751 else if (*ptr
== '\n')
1752 cupsFileWrite(fp
, "\\n", 2);
1753 else /* if (*ptr == '\t') */
1754 cupsFileWrite(fp
, "\\t", 2);
1759 cupsFileWrite(fp
, start
, (size_t)(ptr
- start
));
1764 * 'cups_read_strings()' - Read a pair of strings from a .strings file.
1767 static int /* O - 1 on success, 0 on failure */
1768 cups_read_strings(cups_file_t
*fp
, /* I - .strings file */
1769 int flags
, /* I - CUPS_MESSAGE_xxx flags */
1770 cups_array_t
*a
) /* I - Message catalog array */
1772 char buffer
[8192], /* Line buffer */
1773 *bufptr
, /* Pointer into buffer */
1774 *msg
, /* Pointer to start of message */
1775 *str
; /* Pointer to start of translation string */
1776 _cups_message_t
*m
; /* New message */
1779 while (cupsFileGets(fp
, buffer
, sizeof(buffer
)))
1782 * Skip any line (comments, blanks, etc.) that isn't:
1784 * "message" = "translation";
1787 for (bufptr
= buffer
; *bufptr
&& isspace(*bufptr
& 255); bufptr
++);
1789 if (*bufptr
!= '\"')
1793 * Find the end of the message...
1797 for (msg
= bufptr
; *bufptr
&& *bufptr
!= '\"'; bufptr
++)
1798 if (*bufptr
== '\\' && bufptr
[1])
1806 if (flags
& _CUPS_MESSAGE_UNQUOTE
)
1807 cups_unquote(msg
, msg
);
1810 * Find the start of the translation...
1813 while (*bufptr
&& isspace(*bufptr
& 255))
1820 while (*bufptr
&& isspace(*bufptr
& 255))
1823 if (*bufptr
!= '\"')
1827 * Find the end of the translation...
1831 for (str
= bufptr
; *bufptr
&& *bufptr
!= '\"'; bufptr
++)
1832 if (*bufptr
== '\\' && bufptr
[1])
1840 if (flags
& _CUPS_MESSAGE_UNQUOTE
)
1841 cups_unquote(str
, str
);
1844 * If we get this far we have a valid pair of strings, add them...
1847 if ((m
= malloc(sizeof(_cups_message_t
))) == NULL
)
1850 m
->msg
= strdup(msg
);
1851 m
->str
= strdup(str
);
1853 if (m
->msg
&& m
->str
)
1873 * No more strings...
1881 * 'cups_unquote()' - Unquote characters in strings...
1885 cups_unquote(char *d
, /* O - Unquoted string */
1886 const char *s
) /* I - Original string */
1899 *d
= *d
* 8 + *s
- '0';