1 /*
2 * I18N/language support for CUPS.
3 *
4 * Copyright 2007-2017 by Apple Inc.
5 * Copyright 1997-2007 by Easy Software Products.
6 *
7 * These coded instructions, statements, and computer programs are the
8 * property of Apple Inc. and are protected by Federal copyright
9 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
10 * which should have been included with this file. If this file is
11 * missing or damaged, see the license at "http://www.cups.org/".
12 *
13 * This file is subject to the Apple OS-Developed Software exception.
14 */
15
16 /*
17 * Include necessary headers...
18 */
19
20 #include "cups-private.h"
21 #ifdef HAVE_LANGINFO_H
22 # include <langinfo.h>
23 #endif /* HAVE_LANGINFO_H */
24 #ifdef _WIN32
25 # include <io.h>
26 #else
27 # include <unistd.h>
28 #endif /* _WIN32 */
29 #ifdef HAVE_COREFOUNDATION_H
30 # include <CoreFoundation/CoreFoundation.h>
31 #endif /* HAVE_COREFOUNDATION_H */
32
33
34 /*
35 * Local globals...
36 */
37
38 static _cups_mutex_t lang_mutex = _CUPS_MUTEX_INITIALIZER;
39 /* Mutex to control access to cache */
40 static cups_lang_t *lang_cache = NULL;
41 /* Language string cache */
42 static const char * const lang_encodings[] =
43 { /* Encoding strings */
44 "us-ascii", "iso-8859-1",
45 "iso-8859-2", "iso-8859-3",
46 "iso-8859-4", "iso-8859-5",
47 "iso-8859-6", "iso-8859-7",
48 "iso-8859-8", "iso-8859-9",
49 "iso-8859-10", "utf-8",
50 "iso-8859-13", "iso-8859-14",
51 "iso-8859-15", "cp874",
52 "cp1250", "cp1251",
53 "cp1252", "cp1253",
54 "cp1254", "cp1255",
55 "cp1256", "cp1257",
56 "cp1258", "koi8-r",
57 "koi8-u", "iso-8859-11",
58 "iso-8859-16", "mac",
59 "unknown", "unknown",
60 "unknown", "unknown",
61 "unknown", "unknown",
62 "unknown", "unknown",
63 "unknown", "unknown",
64 "unknown", "unknown",
65 "unknown", "unknown",
66 "unknown", "unknown",
67 "unknown", "unknown",
68 "unknown", "unknown",
69 "unknown", "unknown",
70 "unknown", "unknown",
71 "unknown", "unknown",
72 "unknown", "unknown",
73 "unknown", "unknown",
74 "unknown", "unknown",
75 "unknown", "unknown",
76 "cp932", "cp936",
77 "cp949", "cp950",
78 "cp1361", "unknown",
79 "unknown", "unknown",
80 "unknown", "unknown",
81 "unknown", "unknown",
82 "unknown", "unknown",
83 "unknown", "unknown",
84 "unknown", "unknown",
85 "unknown", "unknown",
86 "unknown", "unknown",
87 "unknown", "unknown",
88 "unknown", "unknown",
89 "unknown", "unknown",
90 "unknown", "unknown",
91 "unknown", "unknown",
92 "unknown", "unknown",
93 "unknown", "unknown",
94 "unknown", "unknown",
95 "unknown", "unknown",
96 "unknown", "unknown",
97 "unknown", "unknown",
98 "unknown", "unknown",
99 "unknown", "unknown",
100 "unknown", "unknown",
101 "unknown", "unknown",
102 "unknown", "unknown",
103 "unknown", "unknown",
104 "unknown", "unknown",
105 "unknown", "unknown",
106 "unknown", "unknown",
107 "unknown", "unknown",
108 "euc-cn", "euc-jp",
109 "euc-kr", "euc-tw",
110 "shift_jisx0213"
111 };
112
113 #ifdef __APPLE__
114 typedef struct
115 {
116 const char * const language; /* Language ID */
117 const char * const locale; /* Locale ID */
118 } _apple_language_locale_t;
119
120 static const _apple_language_locale_t apple_language_locale[] =
121 { /* Language to locale ID LUT */
122 { "en", "en_US" },
123 { "nb", "no" },
124 { "nb_NO", "no" },
125 { "zh-Hans", "zh_CN" },
126 { "zh_HANS", "zh_CN" },
127 { "zh-Hant", "zh_TW" },
128 { "zh_HANT", "zh_TW" },
129 { "zh-Hant_CN", "zh_TW" }
130 };
131 #endif /* __APPLE__ */
132
133
134 /*
135 * Local functions...
136 */
137
138
139 #ifdef __APPLE__
140 static const char *appleLangDefault(void);
141 # ifdef CUPS_BUNDLEDIR
142 # ifndef CF_RETURNS_RETAINED
143 # if __has_feature(attribute_cf_returns_retained)
144 # define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
145 # else
146 # define CF_RETURNS_RETAINED
147 # endif /* __has_feature(attribute_cf_returns_retained) */
148 # endif /* !CF_RETURNED_RETAINED */
149 static cups_array_t *appleMessageLoad(const char *locale)
150 CF_RETURNS_RETAINED;
151 # endif /* CUPS_BUNDLEDIR */
152 #endif /* __APPLE__ */
153 static cups_lang_t *cups_cache_lookup(const char *name,
154 cups_encoding_t encoding);
155 static int cups_message_compare(_cups_message_t *m1,
156 _cups_message_t *m2);
157 static void cups_message_free(_cups_message_t *m);
158 static void cups_message_load(cups_lang_t *lang);
159 static void cups_unquote(char *d, const char *s);
160
161
162 #ifdef __APPLE__
163 /*
164 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
165 * locale ID.
166 */
167
168 const char * /* O - Language ID */
_cupsAppleLanguage(const char * locale,char * language,size_t langsize)169 _cupsAppleLanguage(const char *locale, /* I - Locale ID */
170 char *language,/* I - Language ID buffer */
171 size_t langsize) /* I - Size of language ID buffer */
172 {
173 int i; /* Looping var */
174 CFStringRef localeid, /* CF locale identifier */
175 langid; /* CF language identifier */
176
177
178 /*
179 * Copy the locale name and convert, as needed, to the Apple-specific
180 * locale identifier...
181 */
182
183 switch (strlen(locale))
184 {
185 default :
186 /*
187 * Invalid locale...
188 */
189
190 strlcpy(language, "en", langsize);
191 break;
192
193 case 2 :
194 strlcpy(language, locale, langsize);
195 break;
196
197 case 5 :
198 strlcpy(language, locale, langsize);
199
200 if (language[2] == '-')
201 {
202 /*
203 * Convert ll-cc to ll_CC...
204 */
205
206 language[2] = '_';
207 language[3] = (char)toupper(language[3] & 255);
208 language[4] = (char)toupper(language[4] & 255);
209 }
210 break;
211 }
212
213 for (i = 0;
214 i < (int)(sizeof(apple_language_locale) /
215 sizeof(apple_language_locale[0]));
216 i ++)
217 if (!strcmp(locale, apple_language_locale[i].locale))
218 {
219 strlcpy(language, apple_language_locale[i].language, sizeof(language));
220 break;
221 }
222
223 /*
224 * Attempt to map the locale ID to a language ID...
225 */
226
227 if ((localeid = CFStringCreateWithCString(kCFAllocatorDefault, language,
228 kCFStringEncodingASCII)) != NULL)
229 {
230 if ((langid = CFLocaleCreateCanonicalLanguageIdentifierFromString(
231 kCFAllocatorDefault, localeid)) != NULL)
232 {
233 CFStringGetCString(langid, language, (CFIndex)langsize, kCFStringEncodingASCII);
234 CFRelease(langid);
235 }
236
237 CFRelease(localeid);
238 }
239
240 /*
241 * Return what we got...
242 */
243
244 return (language);
245 }
246
247
248 /*
249 * '_cupsAppleLocale()' - Get the locale associated with an Apple language ID.
250 */
251
252 const char * /* O - Locale */
_cupsAppleLocale(CFStringRef languageName,char * locale,size_t localesize)253 _cupsAppleLocale(CFStringRef languageName, /* I - Apple language ID */
254 char *locale, /* I - Buffer for locale */
255 size_t localesize) /* I - Size of buffer */
256 {
257 int i; /* Looping var */
258 CFStringRef localeName; /* Locale as a CF string */
259 #ifdef DEBUG
260 char temp[1024]; /* Temporary string */
261
262
263 if (!CFStringGetCString(languageName, temp, (CFIndex)sizeof(temp), kCFStringEncodingASCII))
264 temp[0] = '\0';
265
266 DEBUG_printf(("_cupsAppleLocale(languageName=%p(%s), locale=%p, localsize=%d)", (void *)languageName, temp, (void *)locale, (int)localesize));
267 #endif /* DEBUG */
268
269 localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, languageName);
270
271 if (localeName)
272 {
273 /*
274 * Copy the locale name and tweak as needed...
275 */
276
277 if (!CFStringGetCString(localeName, locale, (CFIndex)localesize, kCFStringEncodingASCII))
278 *locale = '\0';
279
280 DEBUG_printf(("_cupsAppleLocale: locale=\"%s\"", locale));
281
282 CFRelease(localeName);
283
284 /*
285 * Map new language identifiers to locales...
286 */
287
288 for (i = 0;
289 i < (int)(sizeof(apple_language_locale) /
290 sizeof(apple_language_locale[0]));
291 i ++)
292 {
293 size_t len = strlen(apple_language_locale[i].language);
294
295 if (!strcmp(locale, apple_language_locale[i].language) ||
296 (!strncmp(locale, apple_language_locale[i].language, len) && (locale[len] == '_' || locale[len] == '-')))
297 {
298 DEBUG_printf(("_cupsAppleLocale: Updating locale to \"%s\".", apple_language_locale[i].locale));
299 strlcpy(locale, apple_language_locale[i].locale, localesize);
300 break;
301 }
302 }
303 }
304 else
305 {
306 /*
307 * Just try the Apple language name...
308 */
309
310 if (!CFStringGetCString(languageName, locale, (CFIndex)localesize, kCFStringEncodingASCII))
311 *locale = '\0';
312 }
313
314 if (!*locale)
315 {
316 DEBUG_puts("_cupsAppleLocale: Returning NULL.");
317 return (NULL);
318 }
319
320 /*
321 * Convert language subtag into region subtag...
322 */
323
324 if (locale[2] == '-')
325 locale[2] = '_';
326 else if (locale[3] == '-')
327 locale[3] = '_';
328
329 if (!strchr(locale, '.'))
330 strlcat(locale, ".UTF-8", localesize);
331
332 DEBUG_printf(("_cupsAppleLocale: Returning \"%s\".", locale));
333
334 return (locale);
335 }
336 #endif /* __APPLE__ */
337
338
339 /*
340 * '_cupsEncodingName()' - Return the character encoding name string
341 * for the given encoding enumeration.
342 */
343
344 const char * /* O - Character encoding */
_cupsEncodingName(cups_encoding_t encoding)345 _cupsEncodingName(
346 cups_encoding_t encoding) /* I - Encoding value */
347 {
348 if (encoding < CUPS_US_ASCII ||
349 encoding >= (cups_encoding_t)(sizeof(lang_encodings) / sizeof(lang_encodings[0])))
350 {
351 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
352 encoding, lang_encodings[0]));
353 return (lang_encodings[0]);
354 }
355 else
356 {
357 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
358 encoding, lang_encodings[encoding]));
359 return (lang_encodings[encoding]);
360 }
361 }
362
363
364 /*
365 * 'cupsLangDefault()' - Return the default language.
366 */
367
368 cups_lang_t * /* O - Language data */
cupsLangDefault(void)369 cupsLangDefault(void)
370 {
371 return (cupsLangGet(NULL));
372 }
373
374
375 /*
376 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
377 * for the given language.
378 */
379
380 const char * /* O - Character encoding */
cupsLangEncoding(cups_lang_t * lang)381 cupsLangEncoding(cups_lang_t *lang) /* I - Language data */
382 {
383 if (lang == NULL)
384 return ((char*)lang_encodings[0]);
385 else
386 return ((char*)lang_encodings[lang->encoding]);
387 }
388
389
390 /*
391 * 'cupsLangFlush()' - Flush all language data out of the cache.
392 */
393
394 void
cupsLangFlush(void)395 cupsLangFlush(void)
396 {
397 cups_lang_t *lang, /* Current language */
398 *next; /* Next language */
399
400
401 /*
402 * Free all languages in the cache...
403 */
404
405 _cupsMutexLock(&lang_mutex);
406
407 for (lang = lang_cache; lang != NULL; lang = next)
408 {
409 /*
410 * Free all messages...
411 */
412
413 _cupsMessageFree(lang->strings);
414
415 /*
416 * Then free the language structure itself...
417 */
418
419 next = lang->next;
420 free(lang);
421 }
422
423 lang_cache = NULL;
424
425 _cupsMutexUnlock(&lang_mutex);
426 }
427
428
429 /*
430 * 'cupsLangFree()' - Free language data.
431 *
432 * This does not actually free anything; use @link cupsLangFlush@ for that.
433 */
434
435 void
cupsLangFree(cups_lang_t * lang)436 cupsLangFree(cups_lang_t *lang) /* I - Language to free */
437 {
438 _cupsMutexLock(&lang_mutex);
439
440 if (lang != NULL && lang->used > 0)
441 lang->used --;
442
443 _cupsMutexUnlock(&lang_mutex);
444 }
445
446
447 /*
448 * 'cupsLangGet()' - Get a language.
449 */
450
451 cups_lang_t * /* O - Language data */
cupsLangGet(const char * language)452 cupsLangGet(const char *language) /* I - Language or locale */
453 {
454 int i; /* Looping var */
455 #ifndef __APPLE__
456 char locale[255]; /* Copy of locale name */
457 #endif /* !__APPLE__ */
458 char langname[16], /* Requested language name */
459 country[16], /* Country code */
460 charset[16], /* Character set */
461 *csptr, /* Pointer to CODESET string */
462 *ptr, /* Pointer into language/charset */
463 real[48]; /* Real language name */
464 cups_encoding_t encoding; /* Encoding to use */
465 cups_lang_t *lang; /* Current language... */
466 static const char * const locale_encodings[] =
467 { /* Locale charset names */
468 "ASCII", "ISO88591", "ISO88592", "ISO88593",
469 "ISO88594", "ISO88595", "ISO88596", "ISO88597",
470 "ISO88598", "ISO88599", "ISO885910", "UTF8",
471 "ISO885913", "ISO885914", "ISO885915", "CP874",
472 "CP1250", "CP1251", "CP1252", "CP1253",
473 "CP1254", "CP1255", "CP1256", "CP1257",
474 "CP1258", "KOI8R", "KOI8U", "ISO885911",
475 "ISO885916", "MACROMAN", "", "",
476
477 "", "", "", "",
478 "", "", "", "",
479 "", "", "", "",
480 "", "", "", "",
481 "", "", "", "",
482 "", "", "", "",
483 "", "", "", "",
484 "", "", "", "",
485
486 "CP932", "CP936", "CP949", "CP950",
487 "CP1361", "", "", "",
488 "", "", "", "",
489 "", "", "", "",
490 "", "", "", "",
491 "", "", "", "",
492 "", "", "", "",
493 "", "", "", "",
494
495 "", "", "", "",
496 "", "", "", "",
497 "", "", "", "",
498 "", "", "", "",
499 "", "", "", "",
500 "", "", "", "",
501 "", "", "", "",
502 "", "", "", "",
503
504 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
505 "SHIFT_JISX0213"
506 };
507
508
509 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language));
510
511 #ifdef __APPLE__
512 /*
513 * Set the character set to UTF-8...
514 */
515
516 strlcpy(charset, "UTF8", sizeof(charset));
517
518 /*
519 * Apple's setlocale doesn't give us the user's localization
520 * preference so we have to look it up this way...
521 */
522
523 if (!language)
524 {
525 if (!getenv("SOFTWARE") || (language = getenv("LANG")) == NULL)
526 language = appleLangDefault();
527
528 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language));
529 }
530
531 #else
532 /*
533 * Set the charset to "unknown"...
534 */
535
536 charset[0] = '\0';
537
538 /*
539 * Use setlocale() to determine the currently set locale, and then
540 * fallback to environment variables to avoid setting the locale,
541 * since setlocale() is not thread-safe!
542 */
543
544 if (!language)
545 {
546 /*
547 * First see if the locale has been set; if it is still "C" or
548 * "POSIX", use the environment to get the default...
549 */
550
551 # ifdef LC_MESSAGES
552 ptr = setlocale(LC_MESSAGES, NULL);
553 # else
554 ptr = setlocale(LC_ALL, NULL);
555 # endif /* LC_MESSAGES */
556
557 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr));
558
559 if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
560 {
561 /*
562 * Get the character set from the LC_CTYPE locale setting...
563 */
564
565 if ((ptr = getenv("LC_CTYPE")) == NULL)
566 if ((ptr = getenv("LC_ALL")) == NULL)
567 if ((ptr = getenv("LANG")) == NULL)
568 ptr = "en_US";
569
570 if ((csptr = strchr(ptr, '.')) != NULL)
571 {
572 /*
573 * Extract the character set from the environment...
574 */
575
576 for (ptr = charset, csptr ++; *csptr; csptr ++)
577 if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr))
578 *ptr++ = *csptr;
579
580 *ptr = '\0';
581 }
582
583 /*
584 * Get the locale for messages from the LC_MESSAGES locale setting...
585 */
586
587 if ((ptr = getenv("LC_MESSAGES")) == NULL)
588 if ((ptr = getenv("LC_ALL")) == NULL)
589 if ((ptr = getenv("LANG")) == NULL)
590 ptr = "en_US";
591 }
592
593 if (ptr)
594 {
595 strlcpy(locale, ptr, sizeof(locale));
596 language = locale;
597
598 /*
599 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
600 */
601
602 if (!strncmp(locale, "nb", 2))
603 locale[1] = 'o';
604
605 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language));
606 }
607 }
608 #endif /* __APPLE__ */
609
610 /*
611 * If "language" is NULL at this point, then chances are we are using
612 * a language that is not installed for the base OS.
613 */
614
615 if (!language)
616 {
617 /*
618 * Switch to the POSIX ("C") locale...
619 */
620
621 language = "C";
622 }
623
624 #ifdef CODESET
625 /*
626 * On systems that support the nl_langinfo(CODESET) call, use
627 * this value as the character set...
628 */
629
630 if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
631 {
632 /*
633 * Copy all of the letters and numbers in the CODESET string...
634 */
635
636 for (ptr = charset; *csptr; csptr ++)
637 if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1))
638 *ptr++ = *csptr;
639
640 *ptr = '\0';
641
642 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
643 "nl_langinfo(CODESET)...", charset));
644 }
645 #endif /* CODESET */
646
647 /*
648 * If we don't have a character set by now, default to UTF-8...
649 */
650
651 if (!charset[0])
652 strlcpy(charset, "UTF8", sizeof(charset));
653
654 /*
655 * Parse the language string passed in to a locale string. "C" is the
656 * standard POSIX locale and is copied unchanged. Otherwise the
657 * language string is converted from ll-cc[.charset] (language-country)
658 * to ll_CC[.CHARSET] to match the file naming convention used by all
659 * POSIX-compliant operating systems. Invalid language names are mapped
660 * to the POSIX locale.
661 */
662
663 country[0] = '\0';
664
665 if (language == NULL || !language[0] ||
666 !strcmp(language, "POSIX"))
667 strlcpy(langname, "C", sizeof(langname));
668 else
669 {
670 /*
671 * Copy the parts of the locale string over safely...
672 */
673
674 for (ptr = langname; *language; language ++)
675 if (*language == '_' || *language == '-' || *language == '.')
676 break;
677 else if (ptr < (langname + sizeof(langname) - 1))
678 *ptr++ = (char)tolower(*language & 255);
679
680 *ptr = '\0';
681
682 if (*language == '_' || *language == '-')
683 {
684 /*
685 * Copy the country code...
686 */
687
688 for (language ++, ptr = country; *language; language ++)
689 if (*language == '.')
690 break;
691 else if (ptr < (country + sizeof(country) - 1))
692 *ptr++ = (char)toupper(*language & 255);
693
694 *ptr = '\0';
695
696 /*
697 * Map Chinese region codes to legacy country codes.
698 */
699
700 if (!strcmp(language, "zh") && !strcmp(country, "HANS"))
701 strlcpy(country, "CN", sizeof(country));
702 if (!strcmp(language, "zh") && !strcmp(country, "HANT"))
703 strlcpy(country, "TW", sizeof(country));
704 }
705
706 if (*language == '.' && !charset[0])
707 {
708 /*
709 * Copy the encoding...
710 */
711
712 for (language ++, ptr = charset; *language; language ++)
713 if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1))
714 *ptr++ = (char)toupper(*language & 255);
715
716 *ptr = '\0';
717 }
718
719 /*
720 * Force a POSIX locale for an invalid language name...
721 */
722
723 if (strlen(langname) != 2 && strlen(langname) != 3)
724 {
725 strlcpy(langname, "C", sizeof(langname));
726 country[0] = '\0';
727 charset[0] = '\0';
728 }
729 }
730
731 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
732 langname, country, charset));
733
734 /*
735 * Figure out the desired encoding...
736 */
737
738 encoding = CUPS_AUTO_ENCODING;
739
740 if (charset[0])
741 {
742 for (i = 0;
743 i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
744 i ++)
745 if (!_cups_strcasecmp(charset, locale_encodings[i]))
746 {
747 encoding = (cups_encoding_t)i;
748 break;
749 }
750
751 if (encoding == CUPS_AUTO_ENCODING)
752 {
753 /*
754 * Map alternate names for various character sets...
755 */
756
757 if (!_cups_strcasecmp(charset, "iso-2022-jp") ||
758 !_cups_strcasecmp(charset, "sjis"))
759 encoding = CUPS_WINDOWS_932;
760 else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
761 encoding = CUPS_WINDOWS_936;
762 else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
763 encoding = CUPS_WINDOWS_949;
764 else if (!_cups_strcasecmp(charset, "big5"))
765 encoding = CUPS_WINDOWS_950;
766 }
767 }
768
769 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding,
770 encoding == CUPS_AUTO_ENCODING ? "auto" :
771 lang_encodings[encoding]));
772
773 /*
774 * See if we already have this language/country loaded...
775 */
776
777 if (country[0])
778 snprintf(real, sizeof(real), "%s_%s", langname, country);
779 else
780 strlcpy(real, langname, sizeof(real));
781
782 _cupsMutexLock(&lang_mutex);
783
784 if ((lang = cups_cache_lookup(real, encoding)) != NULL)
785 {
786 _cupsMutexUnlock(&lang_mutex);
787
788 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real));
789
790 return (lang);
791 }
792
793 /*
794 * See if there is a free language available; if so, use that
795 * record...
796 */
797
798 for (lang = lang_cache; lang != NULL; lang = lang->next)
799 if (lang->used == 0)
800 break;
801
802 if (lang == NULL)
803 {
804 /*
805 * Allocate memory for the language and add it to the cache.
806 */
807
808 if ((lang = calloc(sizeof(cups_lang_t), 1)) == NULL)
809 {
810 _cupsMutexUnlock(&lang_mutex);
811
812 return (NULL);
813 }
814
815 lang->next = lang_cache;
816 lang_cache = lang;
817 }
818 else
819 {
820 /*
821 * Free all old strings as needed...
822 */
823
824 _cupsMessageFree(lang->strings);
825 lang->strings = NULL;
826 }
827
828 /*
829 * Then assign the language and encoding fields...
830 */
831
832 lang->used ++;
833 strlcpy(lang->language, real, sizeof(lang->language));
834
835 if (encoding != CUPS_AUTO_ENCODING)
836 lang->encoding = encoding;
837 else
838 lang->encoding = CUPS_UTF8;
839
840 /*
841 * Return...
842 */
843
844 _cupsMutexUnlock(&lang_mutex);
845
846 return (lang);
847 }
848
849
850 /*
851 * '_cupsLangString()' - Get a message string.
852 *
853 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
854 * convert the string to the language encoding.
855 */
856
857 const char * /* O - Localized message */
_cupsLangString(cups_lang_t * lang,const char * message)858 _cupsLangString(cups_lang_t *lang, /* I - Language */
859 const char *message) /* I - Message */
860 {
861 const char *s; /* Localized message */
862
863
864 DEBUG_printf(("_cupsLangString(lang=%p, message=\"%s\")", (void *)lang, message));
865
866 /*
867 * Range check input...
868 */
869
870 if (!lang || !message || !*message)
871 return (message);
872
873 _cupsMutexLock(&lang_mutex);
874
875 /*
876 * Load the message catalog if needed...
877 */
878
879 if (!lang->strings)
880 cups_message_load(lang);
881
882 s = _cupsMessageLookup(lang->strings, message);
883
884 _cupsMutexUnlock(&lang_mutex);
885
886 return (s);
887 }
888
889
890 /*
891 * '_cupsMessageFree()' - Free a messages array.
892 */
893
894 void
_cupsMessageFree(cups_array_t * a)895 _cupsMessageFree(cups_array_t *a) /* I - Message array */
896 {
897 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
898 /*
899 * Release the cups.strings dictionary as needed...
900 */
901
902 if (cupsArrayUserData(a))
903 CFRelease((CFDictionaryRef)cupsArrayUserData(a));
904 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
905
906 /*
907 * Free the array...
908 */
909
910 cupsArrayDelete(a);
911 }
912
913
914 /*
915 * '_cupsMessageLoad()' - Load a .po file into a messages array.
916 */
917
918 cups_array_t * /* O - New message array */
_cupsMessageLoad(const char * filename,int unquote)919 _cupsMessageLoad(const char *filename, /* I - Message catalog to load */
920 int unquote) /* I - Unescape \foo in strings? */
921 {
922 cups_file_t *fp; /* Message file */
923 cups_array_t *a; /* Message array */
924 _cups_message_t *m; /* Current message */
925 char s[4096], /* String buffer */
926 *ptr, /* Pointer into buffer */
927 *temp; /* New string */
928 size_t length, /* Length of combined strings */
929 ptrlen; /* Length of string */
930
931
932 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename));
933
934 /*
935 * Create an array to hold the messages...
936 */
937
938 if ((a = _cupsMessageNew(NULL)) == NULL)
939 {
940 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
941 return (NULL);
942 }
943
944 /*
945 * Open the message catalog file...
946 */
947
948 if ((fp = cupsFileOpen(filename, "r")) == NULL)
949 {
950 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
951 strerror(errno)));
952 return (a);
953 }
954
955 /*
956 * Read messages from the catalog file until EOF...
957 *
958 * The format is the GNU gettext .po format, which is fairly simple:
959 *
960 * msgid "some text"
961 * msgstr "localized text"
962 *
963 * The ID and localized text can span multiple lines using the form:
964 *
965 * msgid ""
966 * "some long text"
967 * msgstr ""
968 * "localized text spanning "
969 * "multiple lines"
970 */
971
972 m = NULL;
973
974 while (cupsFileGets(fp, s, sizeof(s)) != NULL)
975 {
976 /*
977 * Skip blank and comment lines...
978 */
979
980 if (s[0] == '#' || !s[0])
981 continue;
982
983 /*
984 * Strip the trailing quote...
985 */
986
987 if ((ptr = strrchr(s, '\"')) == NULL)
988 continue;
989
990 *ptr = '\0';
991
992 /*
993 * Find start of value...
994 */
995
996 if ((ptr = strchr(s, '\"')) == NULL)
997 continue;
998
999 ptr ++;
1000
1001 /*
1002 * Unquote the text...
1003 */
1004
1005 if (unquote)
1006 cups_unquote(ptr, ptr);
1007
1008 /*
1009 * Create or add to a message...
1010 */
1011
1012 if (!strncmp(s, "msgid", 5))
1013 {
1014 /*
1015 * Add previous message as needed...
1016 */
1017
1018 if (m)
1019 {
1020 if (m->str && m->str[0])
1021 {
1022 cupsArrayAdd(a, m);
1023 }
1024 else
1025 {
1026 /*
1027 * Translation is empty, don't add it... (STR #4033)
1028 */
1029
1030 free(m->id);
1031 if (m->str)
1032 free(m->str);
1033 free(m);
1034 }
1035 }
1036
1037 /*
1038 * Create a new message with the given msgid string...
1039 */
1040
1041 if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
1042 {
1043 cupsFileClose(fp);
1044 return (a);
1045 }
1046
1047 if ((m->id = strdup(ptr)) == NULL)
1048 {
1049 free(m);
1050 cupsFileClose(fp);
1051 return (a);
1052 }
1053 }
1054 else if (s[0] == '\"' && m)
1055 {
1056 /*
1057 * Append to current string...
1058 */
1059
1060 length = strlen(m->str ? m->str : m->id);
1061 ptrlen = strlen(ptr);
1062
1063 if ((temp = realloc(m->str ? m->str : m->id, length + ptrlen + 1)) == NULL)
1064 {
1065 if (m->str)
1066 free(m->str);
1067 free(m->id);
1068 free(m);
1069
1070 cupsFileClose(fp);
1071 return (a);
1072 }
1073
1074 if (m->str)
1075 {
1076 /*
1077 * Copy the new portion to the end of the msgstr string - safe
1078 * to use memcpy because the buffer is allocated to the correct
1079 * size...
1080 */
1081
1082 m->str = temp;
1083
1084 memcpy(m->str + length, ptr, ptrlen + 1);
1085 }
1086 else
1087 {
1088 /*
1089 * Copy the new portion to the end of the msgid string - safe
1090 * to use memcpy because the buffer is allocated to the correct
1091 * size...
1092 */
1093
1094 m->id = temp;
1095
1096 memcpy(m->id + length, ptr, ptrlen + 1);
1097 }
1098 }
1099 else if (!strncmp(s, "msgstr", 6) && m)
1100 {
1101 /*
1102 * Set the string...
1103 */
1104
1105 if ((m->str = strdup(ptr)) == NULL)
1106 {
1107 free(m->id);
1108 free(m);
1109
1110 cupsFileClose(fp);
1111 return (a);
1112 }
1113 }
1114 }
1115
1116 /*
1117 * Add the last message string to the array as needed...
1118 */
1119
1120 if (m)
1121 {
1122 if (m->str && m->str[0])
1123 {
1124 cupsArrayAdd(a, m);
1125 }
1126 else
1127 {
1128 /*
1129 * Translation is empty, don't add it... (STR #4033)
1130 */
1131
1132 free(m->id);
1133 if (m->str)
1134 free(m->str);
1135 free(m);
1136 }
1137 }
1138
1139 /*
1140 * Close the message catalog file and return the new array...
1141 */
1142
1143 cupsFileClose(fp);
1144
1145 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
1146 cupsArrayCount(a)));
1147
1148 return (a);
1149 }
1150
1151
1152 /*
1153 * '_cupsMessageLookup()' - Lookup a message string.
1154 */
1155
1156 const char * /* O - Localized message */
_cupsMessageLookup(cups_array_t * a,const char * m)1157 _cupsMessageLookup(cups_array_t *a, /* I - Message array */
1158 const char *m) /* I - Message */
1159 {
1160 _cups_message_t key, /* Search key */
1161 *match; /* Matching message */
1162
1163
1164 DEBUG_printf(("_cupsMessageLookup(a=%p, m=\"%s\")", (void *)a, m));
1165
1166 /*
1167 * Lookup the message string; if it doesn't exist in the catalog,
1168 * then return the message that was passed to us...
1169 */
1170
1171 key.id = (char *)m;
1172 match = (_cups_message_t *)cupsArrayFind(a, &key);
1173
1174 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1175 if (!match && cupsArrayUserData(a))
1176 {
1177 /*
1178 * Try looking the string up in the cups.strings dictionary...
1179 */
1180
1181 CFDictionaryRef dict; /* cups.strings dictionary */
1182 CFStringRef cfm, /* Message as a CF string */
1183 cfstr; /* Localized text as a CF string */
1184
1185 dict = (CFDictionaryRef)cupsArrayUserData(a);
1186 cfm = CFStringCreateWithCString(kCFAllocatorDefault, m,
1187 kCFStringEncodingUTF8);
1188 match = calloc(1, sizeof(_cups_message_t));
1189 match->id = strdup(m);
1190 cfstr = cfm ? CFDictionaryGetValue(dict, cfm) : NULL;
1191
1192 if (cfstr)
1193 {
1194 char buffer[1024]; /* Message buffer */
1195
1196 CFStringGetCString(cfstr, buffer, sizeof(buffer), kCFStringEncodingUTF8);
1197 match->str = strdup(buffer);
1198
1199 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...",
1200 m, buffer));
1201 }
1202 else
1203 {
1204 match->str = strdup(m);
1205
1206 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m));
1207 }
1208
1209 cupsArrayAdd(a, match);
1210
1211 if (cfm)
1212 CFRelease(cfm);
1213 }
1214 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1215
1216 if (match && match->str)
1217 return (match->str);
1218 else
1219 return (m);
1220 }
1221
1222
1223 /*
1224 * '_cupsMessageNew()' - Make a new message catalog array.
1225 */
1226
1227 cups_array_t * /* O - Array */
_cupsMessageNew(void * context)1228 _cupsMessageNew(void *context) /* I - User data */
1229 {
1230 return (cupsArrayNew3((cups_array_func_t)cups_message_compare, context,
1231 (cups_ahash_func_t)NULL, 0,
1232 (cups_acopy_func_t)NULL,
1233 (cups_afree_func_t)cups_message_free));
1234 }
1235
1236
1237 #ifdef __APPLE__
1238 /*
1239 * 'appleLangDefault()' - Get the default locale string.
1240 */
1241
1242 static const char * /* O - Locale string */
appleLangDefault(void)1243 appleLangDefault(void)
1244 {
1245 CFBundleRef bundle; /* Main bundle (if any) */
1246 CFArrayRef bundleList; /* List of localizations in bundle */
1247 CFPropertyListRef localizationList = NULL;
1248 /* List of localization data */
1249 CFStringRef languageName; /* Current name */
1250 char *lang; /* LANG environment variable */
1251 _cups_globals_t *cg = _cupsGlobals();
1252 /* Pointer to library globals */
1253
1254
1255 DEBUG_puts("2appleLangDefault()");
1256
1257 /*
1258 * Only do the lookup and translation the first time.
1259 */
1260
1261 if (!cg->language[0])
1262 {
1263 if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL)
1264 {
1265 DEBUG_printf(("3appleLangDefault: Using LANG=%s", lang));
1266 strlcpy(cg->language, lang, sizeof(cg->language));
1267 return (cg->language);
1268 }
1269 else if ((bundle = CFBundleGetMainBundle()) != NULL &&
1270 (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
1271 {
1272 CFURLRef resources = CFBundleCopyResourcesDirectoryURL(bundle);
1273
1274 DEBUG_puts("3appleLangDefault: Getting localizationList from bundle.");
1275
1276 if (resources)
1277 {
1278 CFStringRef cfpath = CFURLCopyPath(resources);
1279 char path[1024];
1280
1281 if (cfpath)
1282 {
1283 /*
1284 * See if we have an Info.plist file in the bundle...
1285 */
1286
1287 CFStringGetCString(cfpath, path, sizeof(path), kCFStringEncodingUTF8);
1288 DEBUG_printf(("3appleLangDefault: Got a resource URL (\"%s\")", path));
1289 strlcat(path, "Contents/Info.plist", sizeof(path));
1290
1291 if (!access(path, R_OK))
1292 localizationList = CFBundleCopyPreferredLocalizationsFromArray(bundleList);
1293 else
1294 DEBUG_puts("3appleLangDefault: No Info.plist, ignoring resource URL...");
1295
1296 CFRelease(cfpath);
1297 }
1298
1299 CFRelease(resources);
1300 }
1301 else
1302 DEBUG_puts("3appleLangDefault: No resource URL.");
1303
1304 CFRelease(bundleList);
1305 }
1306
1307 if (!localizationList)
1308 {
1309 DEBUG_puts("3appleLangDefault: Getting localizationList from preferences.");
1310
1311 localizationList =
1312 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1313 kCFPreferencesCurrentApplication);
1314 }
1315
1316 if (localizationList)
1317 {
1318 #ifdef DEBUG
1319 if (CFGetTypeID(localizationList) == CFArrayGetTypeID())
1320 DEBUG_printf(("3appleLangDefault: Got localizationList, %d entries.",
1321 (int)CFArrayGetCount(localizationList)));
1322 else
1323 DEBUG_puts("3appleLangDefault: Got localizationList but not an array.");
1324 #endif /* DEBUG */
1325
1326 if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
1327 CFArrayGetCount(localizationList) > 0)
1328 {
1329 languageName = CFArrayGetValueAtIndex(localizationList, 0);
1330
1331 if (languageName &&
1332 CFGetTypeID(languageName) == CFStringGetTypeID())
1333 {
1334 if (_cupsAppleLocale(languageName, cg->language, sizeof(cg->language)))
1335 DEBUG_printf(("3appleLangDefault: cg->language=\"%s\"",
1336 cg->language));
1337 else
1338 DEBUG_puts("3appleLangDefault: Unable to get locale.");
1339 }
1340 }
1341
1342 CFRelease(localizationList);
1343 }
1344
1345 /*
1346 * If we didn't find the language, default to en_US...
1347 */
1348
1349 if (!cg->language[0])
1350 {
1351 DEBUG_puts("3appleLangDefault: Defaulting to en_US.");
1352 strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
1353 }
1354 }
1355 else
1356 DEBUG_printf(("3appleLangDefault: Using previous locale \"%s\".", cg->language));
1357
1358 /*
1359 * Return the cached locale...
1360 */
1361
1362 return (cg->language);
1363 }
1364
1365
1366 # ifdef CUPS_BUNDLEDIR
1367 /*
1368 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1369 */
1370
1371 static cups_array_t * /* O - Message catalog */
appleMessageLoad(const char * locale)1372 appleMessageLoad(const char *locale) /* I - Locale ID */
1373 {
1374 char filename[1024], /* Path to cups.strings file */
1375 applelang[256], /* Apple language ID */
1376 baselang[4]; /* Base language */
1377 CFURLRef url; /* URL to cups.strings file */
1378 CFReadStreamRef stream = NULL; /* File stream */
1379 CFPropertyListRef plist = NULL; /* Localization file */
1380 #ifdef DEBUG
1381 const char *cups_strings = getenv("CUPS_STRINGS");
1382 /* Test strings file */
1383 CFErrorRef error = NULL; /* Error when opening file */
1384 #endif /* DEBUG */
1385
1386
1387 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale));
1388
1389 /*
1390 * Load the cups.strings file...
1391 */
1392
1393 #ifdef DEBUG
1394 if (cups_strings)
1395 {
1396 DEBUG_puts("1appleMessageLoad: Using debug CUPS_STRINGS file.");
1397 strlcpy(filename, cups_strings, sizeof(filename));
1398 }
1399 else
1400 #endif /* DEBUG */
1401
1402 snprintf(filename, sizeof(filename),
1403 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings",
1404 _cupsAppleLanguage(locale, applelang, sizeof(applelang)));
1405
1406 if (access(filename, 0))
1407 {
1408 /*
1409 * <rdar://problem/22086642>
1410 *
1411 * Try with original locale string...
1412 */
1413
1414 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
1415 snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
1416 }
1417
1418 if (access(filename, 0))
1419 {
1420 /*
1421 * <rdar://problem/25292403>
1422 *
1423 * Try with just the language code...
1424 */
1425
1426 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
1427
1428 strlcpy(baselang, locale, sizeof(baselang));
1429 if (baselang[3] == '-' || baselang[3] == '_')
1430 baselang[3] = '\0';
1431
1432 snprintf(filename, sizeof(filename), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", baselang);
1433 }
1434
1435 if (access(filename, 0))
1436 {
1437 /*
1438 * Try alternate lproj directory names...
1439 */
1440
1441 DEBUG_printf(("1appleMessageLoad: \"%s\": %s", filename, strerror(errno)));
1442
1443 if (!strncmp(locale, "en", 2))
1444 locale = "English";
1445 else if (!strncmp(locale, "nb", 2))
1446 locale = "no";
1447 else if (!strncmp(locale, "nl", 2))
1448 locale = "Dutch";
1449 else if (!strncmp(locale, "fr", 2))
1450 locale = "French";
1451 else if (!strncmp(locale, "de", 2))
1452 locale = "German";
1453 else if (!strncmp(locale, "it", 2))
1454 locale = "Italian";
1455 else if (!strncmp(locale, "ja", 2))
1456 locale = "Japanese";
1457 else if (!strncmp(locale, "es", 2))
1458 locale = "Spanish";
1459 else if (!strcmp(locale, "zh_HK") || !strncasecmp(locale, "zh-Hant", 7) || !strncasecmp(locale, "zh_Hant", 7))
1460 {
1461 /*
1462 * <rdar://problem/22130168>
1463 * <rdar://problem/27245567>
1464 *
1465 * Try zh_TW first, then zh... Sigh...
1466 */
1467
1468 if (!access(CUPS_BUNDLEDIR "/Resources/zh_TW.lproj/cups.strings", 0))
1469 locale = "zh_TW";
1470 else
1471 locale = "zh";
1472 }
1473 else if (strstr(locale, "_") != NULL || strstr(locale, "-") != NULL)
1474 {
1475 /*
1476 * Drop country code, just try language...
1477 */
1478
1479 strlcpy(baselang, locale, sizeof(baselang));
1480 if (baselang[2] == '-' || baselang[2] == '_')
1481 baselang[2] = '\0';
1482
1483 locale = baselang;
1484 }
1485
1486 snprintf(filename, sizeof(filename),
1487 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
1488 }
1489
1490 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename));
1491
1492 url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
1493 (UInt8 *)filename,
1494 (CFIndex)strlen(filename), false);
1495 if (url)
1496 {
1497 stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
1498 if (stream)
1499 {
1500 /*
1501 * Read the property list containing the localization data.
1502 *
1503 * NOTE: This code currently generates a clang "potential leak"
1504 * warning, but the object is released in _cupsMessageFree().
1505 */
1506
1507 CFReadStreamOpen(stream);
1508
1509 #ifdef DEBUG
1510 plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0,
1511 kCFPropertyListImmutable, NULL,
1512 &error);
1513 if (error)
1514 {
1515 CFStringRef msg = CFErrorCopyDescription(error);
1516 /* Error message */
1517
1518 CFStringGetCString(msg, filename, sizeof(filename),
1519 kCFStringEncodingUTF8);
1520 DEBUG_printf(("1appleMessageLoad: %s", filename));
1521
1522 CFRelease(msg);
1523 CFRelease(error);
1524 }
1525
1526 #else
1527 plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0,
1528 kCFPropertyListImmutable, NULL,
1529 NULL);
1530 #endif /* DEBUG */
1531
1532 if (plist && CFGetTypeID(plist) != CFDictionaryGetTypeID())
1533 {
1534 CFRelease(plist);
1535 plist = NULL;
1536 }
1537
1538 CFRelease(stream);
1539 }
1540
1541 CFRelease(url);
1542 }
1543
1544 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url, stream,
1545 plist));
1546
1547 /*
1548 * Create and return an empty array to act as a cache for messages, passing the
1549 * plist as the user data.
1550 */
1551
1552 return (_cupsMessageNew((void *)plist));
1553 }
1554 # endif /* CUPS_BUNDLEDIR */
1555 #endif /* __APPLE__ */
1556
1557
1558 /*
1559 * 'cups_cache_lookup()' - Lookup a language in the cache...
1560 */
1561
1562 static cups_lang_t * /* O - Language data or NULL */
cups_cache_lookup(const char * name,cups_encoding_t encoding)1563 cups_cache_lookup(
1564 const char *name, /* I - Name of locale */
1565 cups_encoding_t encoding) /* I - Encoding of locale */
1566 {
1567 cups_lang_t *lang; /* Current language */
1568
1569
1570 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name,
1571 encoding, encoding == CUPS_AUTO_ENCODING ? "auto" :
1572 lang_encodings[encoding]));
1573
1574 /*
1575 * Loop through the cache and return a match if found...
1576 */
1577
1578 for (lang = lang_cache; lang != NULL; lang = lang->next)
1579 {
1580 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1581 "encoding=%d(%s)", (void *)lang, lang->language, lang->encoding,
1582 lang_encodings[lang->encoding]));
1583
1584 if (!strcmp(lang->language, name) &&
1585 (encoding == CUPS_AUTO_ENCODING || encoding == lang->encoding))
1586 {
1587 lang->used ++;
1588
1589 DEBUG_puts("8cups_cache_lookup: returning match!");
1590
1591 return (lang);
1592 }
1593 }
1594
1595 DEBUG_puts("8cups_cache_lookup: returning NULL!");
1596
1597 return (NULL);
1598 }
1599
1600
1601 /*
1602 * 'cups_message_compare()' - Compare two messages.
1603 */
1604
1605 static int /* O - Result of comparison */
cups_message_compare(_cups_message_t * m1,_cups_message_t * m2)1606 cups_message_compare(
1607 _cups_message_t *m1, /* I - First message */
1608 _cups_message_t *m2) /* I - Second message */
1609 {
1610 return (strcmp(m1->id, m2->id));
1611 }
1612
1613
1614 /*
1615 * 'cups_message_free()' - Free a message.
1616 */
1617
1618 static void
cups_message_free(_cups_message_t * m)1619 cups_message_free(_cups_message_t *m) /* I - Message */
1620 {
1621 if (m->id)
1622 free(m->id);
1623
1624 if (m->str)
1625 free(m->str);
1626
1627 free(m);
1628 }
1629
1630
1631 /*
1632 * 'cups_message_load()' - Load the message catalog for a language.
1633 */
1634
1635 static void
cups_message_load(cups_lang_t * lang)1636 cups_message_load(cups_lang_t *lang) /* I - Language */
1637 {
1638 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1639 lang->strings = appleMessageLoad(lang->language);
1640
1641 #else
1642 char filename[1024]; /* Filename for language locale file */
1643 _cups_globals_t *cg = _cupsGlobals();
1644 /* Pointer to library globals */
1645
1646
1647 snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
1648 lang->language, lang->language);
1649
1650 if (strchr(lang->language, '_') && access(filename, 0))
1651 {
1652 /*
1653 * Country localization not available, look for generic localization...
1654 */
1655
1656 snprintf(filename, sizeof(filename), "%s/%.2s/cups_%.2s.po", cg->localedir,
1657 lang->language, lang->language);
1658
1659 if (access(filename, 0))
1660 {
1661 /*
1662 * No generic localization, so use POSIX...
1663 */
1664
1665 DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename,
1666 strerror(errno)));
1667
1668 snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir);
1669 }
1670 }
1671
1672 /*
1673 * Read the strings from the file...
1674 */
1675
1676 lang->strings = _cupsMessageLoad(filename, 1);
1677 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
1678 }
1679
1680
1681 /*
1682 * 'cups_unquote()' - Unquote characters in strings...
1683 */
1684
1685 static void
cups_unquote(char * d,const char * s)1686 cups_unquote(char *d, /* O - Unquoted string */
1687 const char *s) /* I - Original string */
1688 {
1689 while (*s)
1690 {
1691 if (*s == '\\')
1692 {
1693 s ++;
1694 if (isdigit(*s))
1695 {
1696 *d = 0;
1697
1698 while (isdigit(*s))
1699 {
1700 *d = *d * 8 + *s - '0';
1701 s ++;
1702 }
1703
1704 d ++;
1705 }
1706 else
1707 {
1708 if (*s == 'n')
1709 *d ++ = '\n';
1710 else if (*s == 'r')
1711 *d ++ = '\r';
1712 else if (*s == 't')
1713 *d ++ = '\t';
1714 else
1715 *d++ = *s;
1716
1717 s ++;
1718 }
1719 }
1720 else
1721 *d++ = *s++;
1722 }
1723
1724 *d = '\0';
1725 }
1726