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