/src/libcups/cups/langprintf.c
Line | Count | Source |
1 | | // |
2 | | // Localized printf/puts functions for CUPS. |
3 | | // |
4 | | // Copyright © 2022-2024 by OpenPrinting. |
5 | | // Copyright © 2007-2014 by Apple Inc. |
6 | | // Copyright © 2002-2007 by Easy Software Products. |
7 | | // |
8 | | // Licensed under Apache License v2.0. See the file "LICENSE" for more |
9 | | // information. |
10 | | // |
11 | | |
12 | | #include "cups-private.h" |
13 | | #ifdef HAVE_LANGINFO_H |
14 | | # include <langinfo.h> |
15 | | #endif // HAVE_LANGINFO_H |
16 | | #ifdef HAVE_COREFOUNDATION_H |
17 | | # include <CoreFoundation/CoreFoundation.h> |
18 | | #endif // HAVE_COREFOUNDATION_H |
19 | | |
20 | | |
21 | | // |
22 | | // Local functions... |
23 | | // |
24 | | |
25 | | static const char *cups_lang_default(void); |
26 | | |
27 | | |
28 | | // |
29 | | // 'cupsLangDefault()' - Return the default language. |
30 | | // |
31 | | |
32 | | cups_lang_t * // O - Language data |
33 | | cupsLangDefault(void) |
34 | 5.23k | { |
35 | 5.23k | _cups_globals_t *cg = _cupsGlobals(); // Global data |
36 | | |
37 | | |
38 | | // Load the default language as needed... |
39 | 5.23k | if (!cg->lang_default) |
40 | 1 | cg->lang_default = cupsLangFind(cups_lang_default()); |
41 | | |
42 | 5.23k | return (cg->lang_default); |
43 | 5.23k | } |
44 | | |
45 | | |
46 | | // |
47 | | // 'cupsLangGetEncoding()' - Get the default encoding for the current locale. |
48 | | // |
49 | | |
50 | | cups_encoding_t // O - Character encoding |
51 | | cupsLangGetEncoding(void) |
52 | 0 | { |
53 | 0 | _cups_globals_t *cg = _cupsGlobals(); // Global data |
54 | | |
55 | |
|
56 | 0 | if (!cg->lang_default) |
57 | 0 | cg->lang_default = cupsLangDefault(); |
58 | |
|
59 | 0 | return (cg->lang_encoding); |
60 | 0 | } |
61 | | |
62 | | |
63 | | // |
64 | | // 'cupsLangPrintf()' - Print a formatted message string to a file. |
65 | | // |
66 | | |
67 | | ssize_t // O - Number of bytes written |
68 | | cupsLangPrintf(FILE *fp, // I - File to write to |
69 | | const char *format, // I - Message string to use |
70 | | ...) // I - Additional arguments as needed |
71 | 0 | { |
72 | 0 | ssize_t bytes; // Number of bytes formatted |
73 | 0 | char buffer[2048], // Message buffer |
74 | 0 | output[8192]; // Output buffer |
75 | 0 | va_list ap; // Pointer to additional arguments |
76 | 0 | _cups_globals_t *cg = _cupsGlobals(); // Global data |
77 | | |
78 | | |
79 | | // Range check... |
80 | 0 | if (!fp || !format) |
81 | 0 | return (-1); |
82 | | |
83 | 0 | if (!cg->lang_default) |
84 | 0 | cg->lang_default = cupsLangDefault(); |
85 | | |
86 | | // Format the string... |
87 | 0 | va_start(ap, format); |
88 | 0 | vsnprintf(buffer, sizeof(buffer) - 1, cupsLangGetString(cg->lang_default, format), ap); |
89 | 0 | va_end(ap); |
90 | |
|
91 | 0 | cupsConcatString(buffer, "\n", sizeof(buffer)); |
92 | | |
93 | | // Transcode to the destination charset... |
94 | 0 | bytes = cupsUTF8ToCharset(output, buffer, sizeof(output), cg->lang_encoding); |
95 | | |
96 | | // Write the string and return the number of bytes written... |
97 | 0 | if (bytes > 0) |
98 | 0 | return ((ssize_t)fwrite(output, 1, (size_t)bytes, fp)); |
99 | 0 | else |
100 | 0 | return (bytes); |
101 | 0 | } |
102 | | |
103 | | |
104 | | // |
105 | | // 'cupsLangPuts()' - Print a static message string to a file. |
106 | | // |
107 | | |
108 | | ssize_t // O - Number of bytes written |
109 | | cupsLangPuts(FILE *fp, // I - File to write to |
110 | | const char *message) // I - Message string to use |
111 | 0 | { |
112 | 0 | ssize_t bytes; // Number of bytes formatted |
113 | 0 | size_t length = 0; // Total length |
114 | 0 | char output[8192]; // Message buffer |
115 | 0 | _cups_globals_t *cg = _cupsGlobals(); // Global data |
116 | | |
117 | | |
118 | | // Range check... |
119 | 0 | if (!fp || !message) |
120 | 0 | return (-1); |
121 | | |
122 | 0 | if (!cg->lang_default) |
123 | 0 | cg->lang_default = cupsLangDefault(); |
124 | | |
125 | | // Transcode to the destination charset... |
126 | 0 | if ((bytes = cupsUTF8ToCharset(output, cupsLangGetString(cg->lang_default, message), sizeof(output) - 4, cg->lang_encoding)) > 0) |
127 | 0 | length += (size_t)bytes; |
128 | 0 | if ((bytes = cupsUTF8ToCharset(output + length, "\n", sizeof(output) - length, cg->lang_encoding)) > 0) |
129 | 0 | length += (size_t)bytes; |
130 | | |
131 | | // Write the string and return the number of bytes written... |
132 | 0 | if (length > 0) |
133 | 0 | return ((ssize_t)fwrite(output, 1, length, fp)); |
134 | 0 | else |
135 | 0 | return (bytes); |
136 | 0 | } |
137 | | |
138 | | |
139 | | // |
140 | | // 'cupsLangSetLocale()' - Set the current locale and transcode the command-line. |
141 | | // |
142 | | |
143 | | void |
144 | | cupsLangSetLocale(char *argv[]) // IO - Command-line arguments |
145 | 0 | { |
146 | 0 | int i; // Looping var |
147 | 0 | char buffer[8192]; // Command-line argument buffer |
148 | 0 | _cups_globals_t *cg = _cupsGlobals(); // Global data |
149 | 0 | #ifdef LC_TIME |
150 | 0 | const char *lc_time; // Current LC_TIME value |
151 | 0 | char new_lc_time[255], // New LC_TIME value |
152 | 0 | *charset; // Pointer to character set |
153 | 0 | #endif // LC_TIME |
154 | | |
155 | | |
156 | | // Set the locale so that times, etc. are displayed properly. |
157 | | // |
158 | | // Unfortunately, while we need the localized time value, we *don't* |
159 | | // want to use the localized charset for the time value, so we need |
160 | | // to set LC_TIME to the locale name with .UTF-8 on the end (if |
161 | | // the locale includes a character set specifier...) |
162 | 0 | setlocale(LC_ALL, ""); |
163 | |
|
164 | 0 | #ifdef LC_TIME |
165 | 0 | if ((lc_time = setlocale(LC_TIME, NULL)) == NULL) |
166 | 0 | lc_time = setlocale(LC_ALL, NULL); |
167 | |
|
168 | 0 | if (lc_time) |
169 | 0 | { |
170 | 0 | cupsCopyString(new_lc_time, lc_time, sizeof(new_lc_time)); |
171 | 0 | if ((charset = strchr(new_lc_time, '.')) == NULL) |
172 | 0 | charset = new_lc_time + strlen(new_lc_time); |
173 | |
|
174 | 0 | cupsCopyString(charset, ".UTF-8", sizeof(new_lc_time) - (size_t)(charset - new_lc_time)); |
175 | 0 | } |
176 | 0 | else |
177 | 0 | cupsCopyString(new_lc_time, "C", sizeof(new_lc_time)); |
178 | |
|
179 | 0 | setlocale(LC_TIME, new_lc_time); |
180 | 0 | #endif // LC_TIME |
181 | | |
182 | | // Initialize the default language info... |
183 | 0 | if (!cg->lang_default) |
184 | 0 | cg->lang_default = cupsLangDefault(); |
185 | | |
186 | | // Transcode the command-line arguments from the locale charset to UTF-8... |
187 | 0 | if (cg->lang_encoding != CUPS_ENCODING_US_ASCII && cg->lang_encoding != CUPS_ENCODING_UTF_8) |
188 | 0 | { |
189 | 0 | for (i = 1; argv[i]; i ++) |
190 | 0 | { |
191 | | // Try converting from the locale charset to UTF-8... |
192 | 0 | if (cupsCharsetToUTF8(buffer, argv[i], sizeof(buffer), cg->lang_encoding) < 0) |
193 | 0 | continue; |
194 | | |
195 | | // Save the new string if it differs from the original... |
196 | 0 | if (strcmp(buffer, argv[i])) |
197 | 0 | argv[i] = strdup(buffer); |
198 | 0 | } |
199 | 0 | } |
200 | 0 | } |
201 | | |
202 | | |
203 | | // |
204 | | // 'cups_lang_default()' - Get the default locale name... |
205 | | // |
206 | | |
207 | | static const char * // O - Locale string |
208 | | cups_lang_default(void) |
209 | 1 | { |
210 | 1 | _cups_globals_t *cg = _cupsGlobals(); |
211 | | // Pointer to library globals |
212 | 1 | #ifndef __APPLE__ |
213 | 1 | static const char * const locale_encodings[] = |
214 | 1 | { // Locale charset names |
215 | 1 | "ASCII", "ISO88591", "ISO88592", "ISO88593", |
216 | 1 | "ISO88594", "ISO88595", "ISO88596", "ISO88597", |
217 | 1 | "ISO88598", "ISO88599", "ISO885910", "UTF8", |
218 | 1 | "ISO885913", "ISO885914", "ISO885915", "CP874", |
219 | 1 | "CP1250", "CP1251", "CP1252", "CP1253", |
220 | 1 | "CP1254", "CP1255", "CP1256", "CP1257", |
221 | 1 | "CP1258", "KOI8R", "KOI8U", "ISO885911", |
222 | 1 | "ISO885916", "MACROMAN", "", "", |
223 | | |
224 | 1 | "", "", "", "", |
225 | 1 | "", "", "", "", |
226 | 1 | "", "", "", "", |
227 | 1 | "", "", "", "", |
228 | 1 | "", "", "", "", |
229 | 1 | "", "", "", "", |
230 | 1 | "", "", "", "", |
231 | 1 | "", "", "", "", |
232 | | |
233 | 1 | "CP932", "CP936", "CP949", "CP950", |
234 | 1 | "CP1361", "GB18030", "", "", |
235 | 1 | "", "", "", "", |
236 | 1 | "", "", "", "", |
237 | 1 | "", "", "", "", |
238 | 1 | "", "", "", "", |
239 | 1 | "", "", "", "", |
240 | 1 | "", "", "", "", |
241 | | |
242 | 1 | "", "", "", "", |
243 | 1 | "", "", "", "", |
244 | 1 | "", "", "", "", |
245 | 1 | "", "", "", "", |
246 | 1 | "", "", "", "", |
247 | 1 | "", "", "", "", |
248 | 1 | "", "", "", "", |
249 | 1 | "", "", "", "", |
250 | | |
251 | 1 | "EUCCN", "EUCJP", "EUCKR", "EUCTW", |
252 | 1 | "SHIFT_JISX0213" |
253 | 1 | }; |
254 | 1 | #endif // !__APPLE__ |
255 | | |
256 | | |
257 | 1 | DEBUG_puts("2cups_lang_default()"); |
258 | | |
259 | | // Only lookup the locale the first time. |
260 | 1 | if (!cg->lang_name[0]) |
261 | 1 | { |
262 | 1 | const char *lang; // LANG environment variable |
263 | | #ifdef __APPLE__ |
264 | | CFBundleRef bundle; // Main bundle (if any) |
265 | | CFArrayRef bundleList; // List of localizations in bundle |
266 | | CFPropertyListRef localizationList = NULL; |
267 | | // List of localization data |
268 | | CFStringRef languageName, // Current language name |
269 | | localeName; // Current locale name |
270 | | |
271 | | if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL) |
272 | | { |
273 | | DEBUG_printf("3cups_lang_default: Using LANG=%s", lang); |
274 | | cupsCopyString(cg->lang_name, lang, sizeof(cg->lang_name)); |
275 | | return (cg->lang_name); |
276 | | } |
277 | | else if ((bundle = CFBundleGetMainBundle()) != NULL && (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL) |
278 | | { |
279 | | CFURLRef resources = CFBundleCopyResourcesDirectoryURL(bundle); |
280 | | // Resource directory for program |
281 | | |
282 | | DEBUG_puts("3cups_lang_default: Getting localizationList from bundle."); |
283 | | |
284 | | if (resources) |
285 | | { |
286 | | CFStringRef cfpath = CFURLCopyPath(resources); |
287 | | // Directory path of bundle |
288 | | char path[1024]; // C string with path |
289 | | |
290 | | if (cfpath) |
291 | | { |
292 | | // See if we have an Info.plist file in the bundle... |
293 | | CFStringGetCString(cfpath, path, sizeof(path), kCFStringEncodingUTF8); |
294 | | DEBUG_printf("3cups_lang_default: Got a resource URL (\"%s\")", path); |
295 | | cupsConcatString(path, "Contents/Info.plist", sizeof(path)); |
296 | | |
297 | | if (!access(path, R_OK)) |
298 | | localizationList = CFBundleCopyPreferredLocalizationsFromArray(bundleList); |
299 | | else |
300 | | DEBUG_puts("3cups_lang_default: No Info.plist, ignoring resource URL..."); |
301 | | |
302 | | CFRelease(cfpath); |
303 | | } |
304 | | |
305 | | CFRelease(resources); |
306 | | } |
307 | | else |
308 | | { |
309 | | DEBUG_puts("3cups_lang_default: No resource URL."); |
310 | | } |
311 | | |
312 | | CFRelease(bundleList); |
313 | | } |
314 | | |
315 | | if (!localizationList) |
316 | | { |
317 | | DEBUG_puts("3cups_lang_default: Getting localizationList from preferences."); |
318 | | |
319 | | localizationList = CFPreferencesCopyAppValue(CFSTR("AppleLanguages"), kCFPreferencesCurrentApplication); |
320 | | } |
321 | | |
322 | | if (localizationList) |
323 | | { |
324 | | # ifdef DEBUG |
325 | | if (CFGetTypeID(localizationList) == CFArrayGetTypeID()) |
326 | | DEBUG_printf("3cups_lang_default: Got localizationList, %d entries.", (int)CFArrayGetCount(localizationList)); |
327 | | else |
328 | | DEBUG_puts("3cups_lang_default: Got localizationList but not an array."); |
329 | | # endif // DEBUG |
330 | | |
331 | | // Make sure the localization list is an array... |
332 | | if (CFGetTypeID(localizationList) == CFArrayGetTypeID() && CFArrayGetCount(localizationList) > 0) |
333 | | { |
334 | | // Make sure the first element is a string... |
335 | | languageName = CFArrayGetValueAtIndex(localizationList, 0); |
336 | | |
337 | | if (languageName && CFGetTypeID(languageName) == CFStringGetTypeID()) |
338 | | { |
339 | | // Get the locale identifier for the given language... |
340 | | if ((localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, languageName)) != NULL) |
341 | | { |
342 | | // Use the locale name... |
343 | | if (!CFStringGetCString(localeName, cg->lang_name, (CFIndex)sizeof(cg->lang_name), kCFStringEncodingASCII)) |
344 | | { |
345 | | // Use default locale... |
346 | | cg->lang_name[0] = '\0'; |
347 | | } |
348 | | |
349 | | CFRelease(localeName); |
350 | | } |
351 | | else if (!CFStringGetCString(languageName, cg->lang_name, (CFIndex)sizeof(cg->lang_name), kCFStringEncodingASCII)) |
352 | | { |
353 | | // Use default locale... |
354 | | cg->lang_name[0] = '\0'; |
355 | | } |
356 | | } |
357 | | } |
358 | | |
359 | | CFRelease(localizationList); |
360 | | } |
361 | | |
362 | | // Always use UTF-8 on macOS... |
363 | | cg->lang_encoding = CUPS_ENCODING_UTF_8; |
364 | | |
365 | | #else // !__APPLE__ |
366 | 1 | size_t i; // Looping var |
367 | 1 | const char *lptr; // Pointer into the language |
368 | 1 | char charset[32], // Character set name |
369 | 1 | *csptr; // Pointer into character set |
370 | | |
371 | | // See if the locale has been set; if it is still "C" or "POSIX", use the |
372 | | // environment to get the default... |
373 | 1 | # ifdef LC_MESSAGES |
374 | 1 | lang = setlocale(LC_MESSAGES, NULL); |
375 | | # else |
376 | | lang = setlocale(LC_ALL, NULL); |
377 | | # endif // LC_MESSAGES |
378 | | |
379 | 1 | DEBUG_printf("3cups_lang_default: Current locale is \"%s\".", lang); |
380 | | |
381 | 1 | charset[0] = '\0'; |
382 | | |
383 | 1 | if (!lang || !strcmp(lang, "C") || !strcmp(lang, "POSIX")) |
384 | 1 | { |
385 | | // Get the character set from the LC_xxx locale setting... |
386 | 1 | if ((lang = getenv("LC_CTYPE")) == NULL) |
387 | 1 | { |
388 | 1 | if ((lang = getenv("LC_ALL")) == NULL) |
389 | 1 | { |
390 | 1 | if ((lang = getenv("LANG")) == NULL) |
391 | 1 | lang = "en_US"; |
392 | 1 | } |
393 | 1 | } |
394 | | |
395 | 1 | if ((lptr = strchr(lang, '.')) != NULL) |
396 | 0 | { |
397 | | // Extract the character set from the environment... |
398 | 0 | for (csptr = charset, lptr ++; *lptr; lptr ++) |
399 | 0 | { |
400 | 0 | if (csptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*lptr)) |
401 | 0 | *csptr++ = *lptr; |
402 | 0 | } |
403 | |
|
404 | 0 | *csptr = '\0'; |
405 | 0 | DEBUG_printf("3cups_lang_default: Charset set to \"%s\" via environment.", charset); |
406 | 0 | } |
407 | | |
408 | | // Get the locale for messages from the LC_MESSAGES locale setting... |
409 | 1 | if ((lang = getenv("LC_MESSAGES")) == NULL) |
410 | 1 | { |
411 | 1 | if ((lang = getenv("LC_ALL")) == NULL) |
412 | 1 | { |
413 | 1 | if ((lang = getenv("LANG")) == NULL) |
414 | 1 | lang = "en_US"; |
415 | 1 | } |
416 | 1 | } |
417 | 1 | } |
418 | | |
419 | 1 | if (lang) |
420 | 1 | { |
421 | | // Copy the language over... |
422 | 1 | cupsCopyString(cg->lang_name, lang, sizeof(cg->lang_name)); |
423 | | |
424 | 1 | if ((lptr = strchr(lang, '.')) != NULL) |
425 | 0 | { |
426 | | // Extract the character set from the environment... |
427 | 0 | for (csptr = charset, lptr ++; *lptr; lptr ++) |
428 | 0 | { |
429 | 0 | if (csptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*lptr)) |
430 | 0 | *csptr++ = *lptr; |
431 | 0 | } |
432 | |
|
433 | 0 | *csptr = '\0'; |
434 | |
|
435 | 0 | DEBUG_printf("3cups_lang_default: Charset set to \"%s\" via setlocale().", charset); |
436 | |
|
437 | 0 | if ((csptr = strchr(cg->lang_name, '.')) != NULL) |
438 | 0 | *csptr = '\0'; // Strip charset from locale name... |
439 | 0 | } |
440 | 1 | } |
441 | | |
442 | 1 | #ifdef CODESET |
443 | | // On systems that support the nl_langinfo(CODESET) call, use this value as |
444 | | // the character set... |
445 | 1 | if (!charset[0] && (lptr = nl_langinfo(CODESET)) != NULL) |
446 | 1 | { |
447 | | // Copy all of the letters and numbers in the CODESET string... |
448 | 15 | for (csptr = charset; *lptr; lptr ++) |
449 | 14 | { |
450 | 14 | if (_cups_isalnum(*lptr) && csptr < (charset + sizeof(charset) - 1)) |
451 | 11 | *csptr++ = *lptr; |
452 | 14 | } |
453 | 1 | *csptr = '\0'; |
454 | | |
455 | 1 | DEBUG_printf("3cups_lang_default: Charset set to \"%s\" via nl_langinfo(CODESET).", charset); |
456 | 1 | } |
457 | 1 | #endif // CODESET |
458 | | |
459 | | // Set the encoding, defaulting to UTF-8... |
460 | 1 | cg->lang_encoding = CUPS_ENCODING_AUTO; |
461 | | |
462 | 134 | for (i = 0; i < (sizeof(locale_encodings) / sizeof(locale_encodings[0])); i ++) |
463 | 133 | { |
464 | 133 | if (!_cups_strcasecmp(charset, locale_encodings[i])) |
465 | 0 | { |
466 | 0 | cg->lang_encoding = (cups_encoding_t)i; |
467 | 0 | break; |
468 | 0 | } |
469 | 133 | } |
470 | | |
471 | 1 | if (cg->lang_encoding == CUPS_ENCODING_AUTO) |
472 | 1 | { |
473 | | // Map alternate names for various character sets... |
474 | 1 | if (!_cups_strcasecmp(charset, "iso-2022-jp") || !_cups_strcasecmp(charset, "sjis")) |
475 | 0 | cg->lang_encoding = CUPS_ENCODING_WINDOWS_932; |
476 | 1 | else if (!_cups_strcasecmp(charset, "iso-2022-cn")) |
477 | 0 | cg->lang_encoding = CUPS_ENCODING_WINDOWS_936; |
478 | 1 | else if (!_cups_strcasecmp(charset, "iso-2022-kr")) |
479 | 0 | cg->lang_encoding = CUPS_ENCODING_WINDOWS_949; |
480 | 1 | else if (!_cups_strcasecmp(charset, "big5")) |
481 | 0 | cg->lang_encoding = CUPS_ENCODING_WINDOWS_950; |
482 | 1 | else |
483 | 1 | cg->lang_encoding = CUPS_ENCODING_UTF_8; |
484 | 1 | } |
485 | 1 | #endif // __APPLE__ |
486 | | |
487 | | // Map default/new locales to their legacy counterparts... |
488 | 1 | if (!cg->lang_name[0] || !strcmp(cg->lang_name, "en")) |
489 | 0 | { |
490 | | // Default to en_US... |
491 | 0 | cupsCopyString(cg->lang_name, "en_US", sizeof(cg->lang_name)); |
492 | 0 | } |
493 | 1 | else if (!strncmp(cg->lang_name, "nb", 2)) |
494 | 0 | { |
495 | | // "nb" == Norwegian Bokmal, "no" is the legacy name... |
496 | 0 | cupsCopyString(cg->lang_name, "no", sizeof(cg->lang_name)); |
497 | 0 | } |
498 | 1 | else if (!strncmp(cg->lang_name, "zh-Hans", 7) || !strncmp(cg->lang_name, "zh_HANS", 7)) |
499 | 0 | { |
500 | | // Simplified Chinese (China) |
501 | 0 | cupsCopyString(cg->lang_name, "zh_CN", sizeof(cg->lang_name)); |
502 | 0 | } |
503 | 1 | else if (!strncmp(cg->lang_name, "zh-Hant", 7) || !strncmp(cg->lang_name, "zh_HANT", 7)) |
504 | 0 | { |
505 | | // Traditional Chinese (Taiwan) |
506 | 0 | cupsCopyString(cg->lang_name, "zh_TW", sizeof(cg->lang_name)); |
507 | 0 | } |
508 | | |
509 | 1 | DEBUG_printf("3cups_lang_default: Using locale \"%s\".", cg->lang_name); |
510 | 1 | } |
511 | 0 | else |
512 | 0 | { |
513 | 0 | DEBUG_printf("3cups_lang_default: Using previous locale \"%s\".", cg->lang_name); |
514 | 0 | } |
515 | | |
516 | | // Return the cached locale... |
517 | 1 | return (cg->lang_name); |
518 | 1 | } |