/src/libcups/cups/language.c
Line | Count | Source |
1 | | // |
2 | | // I18N/language support for CUPS. |
3 | | // |
4 | | // Copyright © 2022-2025 by OpenPrinting. |
5 | | // Copyright © 2007-2017 by Apple Inc. |
6 | | // Copyright © 1997-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 | | #include <sys/stat.h> |
14 | | #if _WIN32 |
15 | | # include <io.h> |
16 | | #else |
17 | | # include <unistd.h> |
18 | | #endif // _WIN32 |
19 | | |
20 | | #include "strings/ca_strings.h" |
21 | | #include "strings/cs_strings.h" |
22 | | #include "strings/da_strings.h" |
23 | | #include "strings/de_strings.h" |
24 | | #include "strings/en_strings.h" |
25 | | #include "strings/es_strings.h" |
26 | | #include "strings/fr_strings.h" |
27 | | #include "strings/it_strings.h" |
28 | | #include "strings/ja_strings.h" |
29 | | #include "strings/pt_BR_strings.h" |
30 | | #include "strings/ru_strings.h" |
31 | | #include "strings/zh_CN_strings.h" |
32 | | |
33 | | |
34 | | // |
35 | | // Types... |
36 | | // |
37 | | |
38 | | typedef struct _cups_message_s // Message catalog entry |
39 | | { |
40 | | char *key, // Key string |
41 | | *text; // Localized text string |
42 | | } _cups_message_t; |
43 | | |
44 | | struct _cups_lang_s // Language Cache |
45 | | { |
46 | | cups_lang_t *next; // Next language in cache |
47 | | cups_rwlock_t rwlock; // Reader/writer lock |
48 | | char language[16]; // Language/locale name |
49 | | size_t num_messages, // Number of messages |
50 | | alloc_messages; // Allocated messages |
51 | | _cups_message_t *messages; // Messages |
52 | | }; |
53 | | |
54 | | |
55 | | // |
56 | | // Local globals... |
57 | | // |
58 | | |
59 | | static cups_mutex_t lang_mutex = CUPS_MUTEX_INITIALIZER; |
60 | | // Mutex to control access to cache |
61 | | static cups_lang_t *lang_cache = NULL; |
62 | | // Language string cache |
63 | | static char *lang_directory = NULL; |
64 | | // Directory for strings files... |
65 | | |
66 | | |
67 | | // |
68 | | // Local functions... |
69 | | // |
70 | | |
71 | | static cups_lang_t *cups_lang_new(const char *language); |
72 | | static int cups_message_compare(_cups_message_t *m1, _cups_message_t *m2); |
73 | | |
74 | | |
75 | | // |
76 | | // 'cupsLangAddStrings()' - Add strings for the specified language. |
77 | | // |
78 | | // This function adds strings for the specified language. It is equivalent to |
79 | | // calling @link cupsLangFind@ followed by @link cupsLangLoadStrings@. |
80 | | // |
81 | | |
82 | | bool // O - `true` on success, `false` on failure |
83 | | cupsLangAddStrings( |
84 | | const char *language, // I - Language name |
85 | | const char *strings) // I - Contents of ".strings" file |
86 | 0 | { |
87 | 0 | cups_lang_t *lang; // Language data |
88 | | |
89 | |
|
90 | 0 | if ((lang = cupsLangFind(language)) != NULL) |
91 | 0 | return (cupsLangLoadStrings(lang, NULL, strings)); |
92 | 0 | else |
93 | 0 | return (false); |
94 | 0 | } |
95 | | |
96 | | |
97 | | // |
98 | | // 'cupsLangFind()' - Find a language localization. |
99 | | // |
100 | | // This function finds the localization information for the specified language |
101 | | // or locale name. |
102 | | // |
103 | | |
104 | | cups_lang_t * // O - Language data |
105 | | cupsLangFind(const char *language) // I - Language or locale name, `NULL` for the current locale |
106 | 0 | { |
107 | 0 | char langname[16]; // Requested language name |
108 | 0 | cups_lang_t *lang; // Current language... |
109 | | |
110 | |
|
111 | 0 | DEBUG_printf("2cupsLangFind(language=\"%s\")", language); |
112 | |
|
113 | 0 | if (!language) |
114 | 0 | return (cupsLangDefault()); |
115 | | |
116 | 0 | cupsMutexLock(&lang_mutex); |
117 | |
|
118 | 0 | cupsCopyString(langname, language, sizeof(langname)); |
119 | 0 | if (langname[2] == '-') |
120 | 0 | langname[2] = '_'; |
121 | |
|
122 | 0 | for (lang = lang_cache; lang; lang = lang->next) |
123 | 0 | { |
124 | 0 | if (!_cups_strcasecmp(lang->language, langname)) |
125 | 0 | break; |
126 | 0 | } |
127 | |
|
128 | 0 | if (!lang) |
129 | 0 | { |
130 | | // Create the language if it doesn't exist... |
131 | 0 | lang = cups_lang_new(langname); |
132 | 0 | } |
133 | |
|
134 | 0 | cupsMutexUnlock(&lang_mutex); |
135 | |
|
136 | 0 | return (lang); |
137 | 0 | } |
138 | | |
139 | | |
140 | | // |
141 | | // 'cupsLangFormatString()' - Create a localized formatted string. |
142 | | // |
143 | | // This function formats a string using the localized version of the "format" |
144 | | // string argument, which supports all of the `printf` format specifiers. |
145 | | // |
146 | | |
147 | | const char * // O - Formatted string |
148 | | cupsLangFormatString( |
149 | | cups_lang_t *lang, // I - Language data |
150 | | char *buffer, // I - Output buffer |
151 | | size_t bufsize, // I - Size of output buffer |
152 | | const char *format, // I - Printf-style format string |
153 | | ...) // I - Additional arguments |
154 | 0 | { |
155 | 0 | va_list ap; // Pointer to additional arguments |
156 | | |
157 | |
|
158 | 0 | va_start(ap, format); |
159 | 0 | vsnprintf(buffer, bufsize, cupsLangGetString(lang, format), ap); |
160 | 0 | va_end(ap); |
161 | |
|
162 | 0 | return (buffer); |
163 | 0 | } |
164 | | |
165 | | |
166 | | // |
167 | | // 'cupsLangGetName()' - Get the language name. |
168 | | // |
169 | | |
170 | | const char * // O - Language name |
171 | | cupsLangGetName(cups_lang_t *lang) // I - Language data |
172 | 0 | { |
173 | 0 | return (lang ? lang->language : NULL); |
174 | 0 | } |
175 | | |
176 | | |
177 | | // |
178 | | // 'cupsLangGetString()' - Get a localized message string. |
179 | | // |
180 | | // This function gets a localized UTF-8 message string for the specified |
181 | | // language. If the message is not localized, the original message pointer is |
182 | | // returned. |
183 | | // |
184 | | |
185 | | const char * // O - Localized message |
186 | | cupsLangGetString(cups_lang_t *lang, // I - Language |
187 | | const char *message) // I - Message |
188 | 0 | { |
189 | 0 | _cups_message_t key, // Search key |
190 | 0 | *match; // Matching message |
191 | 0 | const char *text; // Localized message text |
192 | | |
193 | |
|
194 | 0 | DEBUG_printf("cupsLangGetString(lang=%p(%s), message=\"%s\")", (void *)lang, lang ? lang->language : "null", message); |
195 | | |
196 | | // Range check input... |
197 | 0 | if (!lang || !lang->num_messages || !message || !*message) |
198 | 0 | return (message); |
199 | | |
200 | 0 | cupsRWLockRead(&lang->rwlock); |
201 | |
|
202 | 0 | key.key = (char *)message; |
203 | 0 | match = bsearch(&key, lang->messages, lang->num_messages, sizeof(_cups_message_t), (int (*)(const void *, const void *))cups_message_compare); |
204 | 0 | text = match ? match->text : message; |
205 | |
|
206 | 0 | cupsRWUnlock(&lang->rwlock); |
207 | |
|
208 | 0 | return (text); |
209 | 0 | } |
210 | | |
211 | | |
212 | | // |
213 | | // 'cupsLangIsRTL()' - Get the writing direction. |
214 | | // |
215 | | // This function gets the writing direction for the specified language. |
216 | | // |
217 | | |
218 | | bool // O - `true` if right-to-left, `false` if left-to-right |
219 | | cupsLangIsRTL(cups_lang_t *lang) // I - Language |
220 | 0 | { |
221 | | // Range check input... |
222 | 0 | if (!lang) |
223 | 0 | return (false); |
224 | | |
225 | | // The following languages are written from right to left: Arabic, Aramaic, |
226 | | // Azeri, Divehi, Fulah, Hebrew, Kurdish, N'ko, Persian, Rohingya, Syriac, and |
227 | | // Urdu. Not all of these have language codes... |
228 | 0 | return (!strncmp(lang->language, "ar", 2) || !strncmp(lang->language, "dv", 2) || !strncmp(lang->language, "ff", 2) || !strncmp(lang->language, "he", 2) || !strncmp(lang->language, "ku", 2) || !strncmp(lang->language, "fa", 2) || !strncmp(lang->language, "ur", 2)); |
229 | 0 | } |
230 | | |
231 | | |
232 | | // |
233 | | // 'cupsLangLoadStrings()' - Load a message catalog for a language. |
234 | | // |
235 | | // This function loads a message catalog for a language. The catalog must be in |
236 | | // the Apple .strings format. For example, the following translates the strings |
237 | | // "Yes", "No", and "Welcome, %s." to French: |
238 | | // |
239 | | // ``` |
240 | | // "Yes" = "Oui"; |
241 | | // "No" = "Non"; |
242 | | // "Welcome, %s." = "Bievenue, %s."; |
243 | | // ``` |
244 | | // |
245 | | |
246 | | bool // O - `true` on success, `false` on failure |
247 | | cupsLangLoadStrings( |
248 | | cups_lang_t *lang, // I - Language data |
249 | | const char *filename, // I - Filename or `NULL` for none |
250 | | const char *strings) // I - Strings or `NULL` for none |
251 | 0 | { |
252 | 0 | bool ret = true; // Return value |
253 | 0 | int linenum; // Current line number in data |
254 | 0 | const char *data, // Pointer to strings data |
255 | 0 | *dataptr; // Pointer into string data |
256 | 0 | char key[1024], // Key string |
257 | 0 | text[1024], // Localized text string |
258 | 0 | *ptr; // Pointer into strings |
259 | 0 | _cups_message_t *m, // Pointer to message |
260 | 0 | mkey; // Search key |
261 | 0 | size_t num_messages; // New number of messages |
262 | | |
263 | |
|
264 | 0 | if (filename) |
265 | 0 | { |
266 | | // Load the strings file... |
267 | 0 | int fd; // File descriptor |
268 | 0 | struct stat fileinfo; // File information |
269 | 0 | ssize_t bytes; // Bytes read |
270 | |
|
271 | 0 | if ((fd = open(filename, O_RDONLY)) < 0) |
272 | 0 | { |
273 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); |
274 | 0 | return (false); |
275 | 0 | } |
276 | | |
277 | 0 | if (fstat(fd, &fileinfo)) |
278 | 0 | { |
279 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); |
280 | 0 | close(fd); |
281 | 0 | return (false); |
282 | 0 | } |
283 | | |
284 | 0 | if ((ptr = malloc((size_t)(fileinfo.st_size + 1))) == NULL) |
285 | 0 | { |
286 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); |
287 | 0 | close(fd); |
288 | 0 | return (false); |
289 | 0 | } |
290 | | |
291 | 0 | if ((bytes = read(fd, ptr, (size_t)fileinfo.st_size)) < 0) |
292 | 0 | { |
293 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); |
294 | 0 | close(fd); |
295 | 0 | free(ptr); |
296 | 0 | return (false); |
297 | 0 | } |
298 | | |
299 | 0 | close(fd); |
300 | |
|
301 | 0 | ptr[bytes] = '\0'; |
302 | 0 | data = ptr; |
303 | 0 | } |
304 | 0 | else |
305 | 0 | { |
306 | | // Use in-memory strings data... |
307 | 0 | data = (const char *)strings; |
308 | 0 | } |
309 | | |
310 | 0 | if (!data) |
311 | 0 | { |
312 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); |
313 | 0 | return (false); |
314 | 0 | } |
315 | | |
316 | | // Scan the in-memory strings data and add key/text pairs... |
317 | | // |
318 | | // Format of strings files is: |
319 | | // |
320 | | // "key" = "text"; |
321 | 0 | cupsRWLockWrite(&lang->rwlock); |
322 | |
|
323 | 0 | num_messages = lang->num_messages; |
324 | 0 | mkey.key = key; |
325 | |
|
326 | 0 | for (dataptr = data, linenum = 1; *dataptr; dataptr ++) |
327 | 0 | { |
328 | | // Skip leading whitespace... |
329 | 0 | while (*dataptr && isspace(*dataptr & 255)) |
330 | 0 | { |
331 | 0 | if (*dataptr == '\n') |
332 | 0 | linenum ++; |
333 | |
|
334 | 0 | dataptr ++; |
335 | 0 | } |
336 | |
|
337 | 0 | if (!*dataptr) |
338 | 0 | { |
339 | | // End of string... |
340 | 0 | break; |
341 | 0 | } |
342 | 0 | else if (*dataptr == '/' && dataptr[1] == '*') |
343 | 0 | { |
344 | | // Start of C-style comment... |
345 | 0 | for (dataptr += 2; *dataptr; dataptr ++) |
346 | 0 | { |
347 | 0 | if (*dataptr == '*' && dataptr[1] == '/') |
348 | 0 | { |
349 | 0 | dataptr += 2; |
350 | 0 | break; |
351 | 0 | } |
352 | 0 | else if (*dataptr == '\n') |
353 | 0 | linenum ++; |
354 | 0 | } |
355 | |
|
356 | 0 | if (!*dataptr) |
357 | 0 | break; |
358 | 0 | } |
359 | 0 | else if (*dataptr != '\"') |
360 | 0 | { |
361 | | // Something else we don't recognize... |
362 | 0 | snprintf(text, sizeof(text), "Syntax error on line %d of '%s'.", linenum, filename ? filename : "in-memory"); |
363 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0); |
364 | 0 | ret = false; |
365 | 0 | break; |
366 | 0 | } |
367 | | |
368 | | // Parse key string... |
369 | 0 | dataptr ++; |
370 | 0 | for (ptr = key; *dataptr && *dataptr != '\"'; dataptr ++) |
371 | 0 | { |
372 | 0 | if (*dataptr == '\\' && dataptr[1]) |
373 | 0 | { |
374 | | // Escaped character... |
375 | 0 | int ch; // Character |
376 | |
|
377 | 0 | dataptr ++; |
378 | 0 | if (*dataptr == '\\' || *dataptr == '\'' || *dataptr == '\"') |
379 | 0 | { |
380 | 0 | ch = *dataptr; |
381 | 0 | } |
382 | 0 | else if (*dataptr == 'n') |
383 | 0 | { |
384 | 0 | ch = '\n'; |
385 | 0 | } |
386 | 0 | else if (*dataptr == 'r') |
387 | 0 | { |
388 | 0 | ch = '\r'; |
389 | 0 | } |
390 | 0 | else if (*dataptr == 't') |
391 | 0 | { |
392 | 0 | ch = '\t'; |
393 | 0 | } |
394 | 0 | else if (*dataptr >= '0' && *dataptr <= '3' && dataptr[1] >= '0' && dataptr[1] <= '7' && dataptr[2] >= '0' && dataptr[2] <= '7') |
395 | 0 | { |
396 | | // Octal escape |
397 | 0 | ch = ((*dataptr - '0') << 6) | ((dataptr[1] - '0') << 3) | (dataptr[2] - '0'); |
398 | 0 | dataptr += 2; |
399 | 0 | } |
400 | 0 | else |
401 | 0 | { |
402 | 0 | snprintf(text, sizeof(text), "Invalid escape in key string on line %d of '%s'.", linenum, filename ? filename : "in-memory"); |
403 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0); |
404 | 0 | ret = false; |
405 | 0 | break; |
406 | 0 | } |
407 | | |
408 | 0 | if (ptr < (key + sizeof(key) - 1)) |
409 | 0 | *ptr++ = (char)ch; |
410 | 0 | } |
411 | 0 | else if (ptr < (key + sizeof(key) - 1)) |
412 | 0 | { |
413 | 0 | *ptr++ = *dataptr; |
414 | 0 | } |
415 | 0 | } |
416 | |
|
417 | 0 | if (!*dataptr) |
418 | 0 | { |
419 | 0 | snprintf(text, sizeof(text), "Unterminated key string on line %d of '%s'.", linenum, filename ? filename : "in-memory"); |
420 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0); |
421 | 0 | ret = false; |
422 | 0 | break; |
423 | 0 | } |
424 | | |
425 | 0 | dataptr ++; |
426 | 0 | *ptr = '\0'; |
427 | | |
428 | | // Parse separator... |
429 | 0 | while (*dataptr && isspace(*dataptr & 255)) |
430 | 0 | { |
431 | 0 | if (*dataptr == '\n') |
432 | 0 | linenum ++; |
433 | |
|
434 | 0 | dataptr ++; |
435 | 0 | } |
436 | |
|
437 | 0 | if (*dataptr != '=') |
438 | 0 | { |
439 | 0 | snprintf(text, sizeof(text), "Missing separator on line %d of '%s'.", linenum, filename ? filename : "in-memory"); |
440 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0); |
441 | 0 | ret = false; |
442 | 0 | break; |
443 | 0 | } |
444 | | |
445 | 0 | dataptr ++; |
446 | 0 | while (*dataptr && isspace(*dataptr & 255)) |
447 | 0 | { |
448 | 0 | if (*dataptr == '\n') |
449 | 0 | linenum ++; |
450 | |
|
451 | 0 | dataptr ++; |
452 | 0 | } |
453 | |
|
454 | 0 | if (*dataptr != '\"') |
455 | 0 | { |
456 | 0 | snprintf(text, sizeof(text), "Missing text string on line %d of '%s'.", linenum, filename ? filename : "in-memory"); |
457 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0); |
458 | 0 | ret = false; |
459 | 0 | break; |
460 | 0 | } |
461 | | |
462 | | // Parse text string... |
463 | 0 | dataptr ++; |
464 | 0 | for (ptr = text; *dataptr && *dataptr != '\"'; dataptr ++) |
465 | 0 | { |
466 | 0 | if (*dataptr == '\\') |
467 | 0 | { |
468 | | // Escaped character... |
469 | 0 | int ch; // Character |
470 | |
|
471 | 0 | dataptr ++; |
472 | 0 | if (*dataptr == '\\' || *dataptr == '\'' || *dataptr == '\"') |
473 | 0 | { |
474 | 0 | ch = *dataptr; |
475 | 0 | } |
476 | 0 | else if (*dataptr == 'n') |
477 | 0 | { |
478 | 0 | ch = '\n'; |
479 | 0 | } |
480 | 0 | else if (*dataptr == 'r') |
481 | 0 | { |
482 | 0 | ch = '\r'; |
483 | 0 | } |
484 | 0 | else if (*dataptr == 't') |
485 | 0 | { |
486 | 0 | ch = '\t'; |
487 | 0 | } |
488 | 0 | else if (*dataptr >= '0' && *dataptr <= '3' && dataptr[1] >= '0' && dataptr[1] <= '7' && dataptr[2] >= '0' && dataptr[2] <= '7') |
489 | 0 | { |
490 | | // Octal escape |
491 | 0 | ch = ((*dataptr - '0') << 6) | ((dataptr[1] - '0') << 3) | (dataptr[2] - '0'); |
492 | 0 | dataptr += 2; |
493 | 0 | } |
494 | 0 | else |
495 | 0 | { |
496 | 0 | snprintf(text, sizeof(text), "Invalid escape in text string on line %d of '%s'.", linenum, filename ? filename : "in-memory"); |
497 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0); |
498 | 0 | ret = false; |
499 | 0 | break; |
500 | 0 | } |
501 | | |
502 | 0 | if (ptr < (text + sizeof(text) - 1)) |
503 | 0 | *ptr++ = (char)ch; |
504 | 0 | } |
505 | 0 | else if (ptr < (text + sizeof(text) - 1)) |
506 | 0 | { |
507 | 0 | *ptr++ = *dataptr; |
508 | 0 | } |
509 | 0 | } |
510 | |
|
511 | 0 | if (!*dataptr) |
512 | 0 | { |
513 | 0 | snprintf(text, sizeof(text), "Unterminated text string on line %d of '%s'.", linenum, filename ? filename : "in-memory"); |
514 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0); |
515 | 0 | ret = false; |
516 | 0 | break; |
517 | 0 | } |
518 | | |
519 | 0 | dataptr ++; |
520 | 0 | *ptr = '\0'; |
521 | | |
522 | | // Look for terminator, then add the pair... |
523 | 0 | if (*dataptr != ';') |
524 | 0 | { |
525 | 0 | snprintf(text, sizeof(text), "Missing terminator on line %d of '%s'.", linenum, filename ? filename : "in-memory"); |
526 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, text, 0); |
527 | 0 | ret = false; |
528 | 0 | break; |
529 | 0 | } |
530 | | |
531 | 0 | dataptr ++; |
532 | | |
533 | | // Add the message if it doesn't already exist... |
534 | 0 | if (lang->num_messages > 0 && bsearch(&mkey, lang->messages, lang->num_messages, sizeof(_cups_message_t), (int (*)(const void *, const void *))cups_message_compare)) |
535 | 0 | continue; |
536 | | |
537 | 0 | if (num_messages >= lang->alloc_messages) |
538 | 0 | { |
539 | 0 | if ((m = realloc(lang->messages, (lang->alloc_messages + 1024) * sizeof(_cups_message_t))) == NULL) |
540 | 0 | { |
541 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); |
542 | 0 | ret = false; |
543 | 0 | break; |
544 | 0 | } |
545 | | |
546 | 0 | lang->messages = m; |
547 | 0 | lang->alloc_messages += 1024; |
548 | 0 | } |
549 | | |
550 | 0 | m = lang->messages + num_messages; |
551 | |
|
552 | 0 | if ((m->key = _cupsStrAlloc(key)) == NULL || (m->text = _cupsStrAlloc(text)) == NULL) |
553 | 0 | { |
554 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); |
555 | 0 | _cupsStrFree(m->key); |
556 | 0 | _cupsStrFree(m->text); |
557 | 0 | ret = false; |
558 | 0 | break; |
559 | 0 | } |
560 | | |
561 | 0 | num_messages ++; |
562 | 0 | } |
563 | | |
564 | | // Re-sort messages as needed... |
565 | 0 | if (num_messages > lang->num_messages) |
566 | 0 | { |
567 | 0 | lang->num_messages = num_messages; |
568 | 0 | qsort(lang->messages, lang->num_messages, sizeof(_cups_message_t), (int (*)(const void *, const void *))cups_message_compare); |
569 | 0 | } |
570 | |
|
571 | 0 | cupsRWUnlock(&lang->rwlock); |
572 | | |
573 | | // Free temporary storage and return... |
574 | 0 | if (data != strings) |
575 | 0 | free((void *)data); |
576 | |
|
577 | 0 | return (ret); |
578 | 0 | } |
579 | | |
580 | | |
581 | | // |
582 | | // 'cupsLangSetDirectory()' - Set the directory containing localizations. |
583 | | // |
584 | | // This function sets the directory where .strings files can be found. The |
585 | | // files are named "LOCALE.string" where "LOCALE" is the locale name, for |
586 | | // example "fr" for generic French and "fr-CA" for Canadian French. |
587 | | // |
588 | | |
589 | | void |
590 | | cupsLangSetDirectory(const char *d) // I - Directory name |
591 | 0 | { |
592 | 0 | if (d) |
593 | 0 | { |
594 | 0 | cupsMutexLock(&lang_mutex); |
595 | |
|
596 | 0 | free(lang_directory); |
597 | 0 | lang_directory = strdup(d); |
598 | |
|
599 | 0 | cupsMutexUnlock(&lang_mutex); |
600 | 0 | } |
601 | 0 | } |
602 | | |
603 | | |
604 | | // |
605 | | // 'cups_lang_new()' - Create a new language. |
606 | | // |
607 | | |
608 | | static cups_lang_t * // O - Language data |
609 | | cups_lang_new(const char *language) // I - Language name |
610 | 0 | { |
611 | 0 | cups_lang_t *lang; // Language data |
612 | 0 | char filename[1024]; // Strings file... |
613 | 0 | bool status; // Load status |
614 | | |
615 | | |
616 | | // Create an empty language data structure... |
617 | 0 | if ((lang = calloc(1, sizeof(cups_lang_t))) == NULL) |
618 | 0 | return (NULL); |
619 | | |
620 | 0 | cupsRWInit(&lang->rwlock); |
621 | 0 | cupsCopyString(lang->language, language, sizeof(lang->language)); |
622 | | |
623 | | // Add strings... |
624 | 0 | if (!_cups_strncasecmp(language, "ca", 2)) |
625 | 0 | status = cupsLangLoadStrings(lang, NULL, ca_strings); |
626 | 0 | else if (!_cups_strncasecmp(language, "cs", 2)) |
627 | 0 | status = cupsLangLoadStrings(lang, NULL, cs_strings); |
628 | 0 | else if (!_cups_strncasecmp(language, "da", 2)) |
629 | 0 | status = cupsLangLoadStrings(lang, NULL, da_strings); |
630 | 0 | else if (!_cups_strncasecmp(language, "de", 2)) |
631 | 0 | status = cupsLangLoadStrings(lang, NULL, de_strings); |
632 | 0 | else if (!_cups_strncasecmp(language, "es", 2)) |
633 | 0 | status = cupsLangLoadStrings(lang, NULL, es_strings); |
634 | 0 | else if (!_cups_strncasecmp(language, "fr", 2)) |
635 | 0 | status = cupsLangLoadStrings(lang, NULL, fr_strings); |
636 | 0 | else if (!_cups_strncasecmp(language, "it", 2)) |
637 | 0 | status = cupsLangLoadStrings(lang, NULL, it_strings); |
638 | 0 | else if (!_cups_strncasecmp(language, "ja", 2)) |
639 | 0 | status = cupsLangLoadStrings(lang, NULL, ja_strings); |
640 | 0 | else if (!_cups_strncasecmp(language, "pt", 2)) |
641 | 0 | status = cupsLangLoadStrings(lang, NULL, pt_BR_strings); |
642 | 0 | else if (!_cups_strncasecmp(language, "ru", 2)) |
643 | 0 | status = cupsLangLoadStrings(lang, NULL, ru_strings); |
644 | 0 | else if (!_cups_strncasecmp(language, "zh", 2)) |
645 | 0 | status = cupsLangLoadStrings(lang, NULL, zh_CN_strings); |
646 | 0 | else |
647 | 0 | status = cupsLangLoadStrings(lang, NULL, en_strings); |
648 | |
|
649 | 0 | if (status && lang_directory) |
650 | 0 | { |
651 | 0 | snprintf(filename, sizeof(filename), "%s/%s.strings", lang_directory, language); |
652 | 0 | if (access(filename, 0) && language[2]) |
653 | 0 | { |
654 | 0 | char baselang[3]; // Base language name |
655 | |
|
656 | 0 | cupsCopyString(baselang, language, sizeof(baselang)); |
657 | 0 | snprintf(filename, sizeof(filename), "%s/%s.strings", lang_directory, baselang); |
658 | 0 | } |
659 | |
|
660 | 0 | if (!access(filename, 0)) |
661 | 0 | status = cupsLangLoadStrings(lang, filename, NULL); |
662 | 0 | } |
663 | |
|
664 | 0 | if (!status) |
665 | 0 | { |
666 | | // Free memory if the load failed... |
667 | 0 | size_t i; // Looping var |
668 | |
|
669 | 0 | for (i = 0; i < lang->num_messages; i ++) |
670 | 0 | { |
671 | 0 | _cupsStrFree(lang->messages[i].key); |
672 | 0 | _cupsStrFree(lang->messages[i].text); |
673 | 0 | } |
674 | |
|
675 | 0 | free(lang->messages); |
676 | 0 | free(lang); |
677 | |
|
678 | 0 | return (NULL); |
679 | 0 | } |
680 | | |
681 | | // Add this language to the front of the list... |
682 | 0 | lang->next = lang_cache; |
683 | 0 | lang_cache = lang; |
684 | |
|
685 | 0 | return (lang); |
686 | 0 | } |
687 | | |
688 | | |
689 | | // |
690 | | // 'cups_message_compare()' - Compare two messages. |
691 | | // |
692 | | |
693 | | static int // O - Result of comparison |
694 | | cups_message_compare( |
695 | | _cups_message_t *m1, // I - First message |
696 | | _cups_message_t *m2) // I - Second message |
697 | 0 | { |
698 | 0 | return (strcmp(m1->key, m2->key)); |
699 | 0 | } |