]> git.ipfire.org Git - thirdparty/cups.git/blame - cups/language.c
Fix bad merge.
[thirdparty/cups.git] / cups / language.c
CommitLineData
ef416fc2 1/*
75bd9771 2 * "$Id: language.c 7558 2008-05-12 23:46:44Z mike $"
ef416fc2 3 *
71e16022 4 * I18N/language support for CUPS.
ef416fc2 5 *
71e16022 6 * Copyright 2007-2010 by Apple Inc.
b86bc4cf 7 * Copyright 1997-2007 by Easy Software Products.
ef416fc2 8 *
9 * These coded instructions, statements, and computer programs are the
bc44d920 10 * property of Apple Inc. and are protected by Federal copyright
11 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
12 * which should have been included with this file. If this file is
13 * file is missing or damaged, see the license at "http://www.cups.org/".
ef416fc2 14 *
15 * This file is subject to the Apple OS-Developed Software exception.
16 *
17 * Contents:
18 *
ef416fc2 19 */
20
21/*
22 * Include necessary headers...
23 */
24
71e16022 25#include "cups-private.h"
ef416fc2 26#ifdef HAVE_LANGINFO_H
27# include <langinfo.h>
28#endif /* HAVE_LANGINFO_H */
29#ifdef WIN32
30# include <io.h>
31#else
32# include <unistd.h>
33#endif /* WIN32 */
fa73b229 34#ifdef HAVE_COREFOUNDATION_H
35# include <CoreFoundation/CoreFoundation.h>
36#endif /* HAVE_COREFOUNDATION_H */
ef416fc2 37
38
d6ae789d 39/*
40 * Local globals...
41 */
42
6d2f911b 43static _cups_mutex_t lang_mutex = _CUPS_MUTEX_INITIALIZER;
d6ae789d 44 /* Mutex to control access to cache */
d6ae789d 45static cups_lang_t *lang_cache = NULL;
46 /* Language string cache */
ef416fc2 47static const char * const lang_encodings[] =
48 { /* Encoding strings */
49 "us-ascii", "iso-8859-1",
50 "iso-8859-2", "iso-8859-3",
51 "iso-8859-4", "iso-8859-5",
52 "iso-8859-6", "iso-8859-7",
53 "iso-8859-8", "iso-8859-9",
54 "iso-8859-10", "utf-8",
55 "iso-8859-13", "iso-8859-14",
cc754834
MS
56 "iso-8859-15", "cp874",
57 "cp1250", "cp1251",
58 "cp1252", "cp1253",
59 "cp1254", "cp1255",
60 "cp1256", "cp1257",
61 "cp1258", "koi8-r",
ef416fc2 62 "koi8-u", "iso-8859-11",
bd7854cb 63 "iso-8859-16", "mac-roman",
ef416fc2 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 "unknown", "unknown",
77 "unknown", "unknown",
78 "unknown", "unknown",
79 "unknown", "unknown",
80 "unknown", "unknown",
cc754834
MS
81 "cp932", "cp936",
82 "cp949", "cp950",
83 "cp1361", "unknown",
ef416fc2 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 "unknown", "unknown",
109 "unknown", "unknown",
110 "unknown", "unknown",
111 "unknown", "unknown",
112 "unknown", "unknown",
113 "euc-cn", "euc-jp",
4b3f67ff
MS
114 "euc-kr", "euc-tw",
115 "jis-x0213"
ef416fc2 116 };
117
0a682745
MS
118#ifdef __APPLE__
119typedef struct
120{
121 const char * const language; /* Language ID */
122 const char * const locale; /* Locale ID */
123} _apple_language_locale_t;
124
125static const _apple_language_locale_t apple_language_locale[] =
126{ /* Locale to language ID LUT */
0837b7e8
MS
127 { "en", "en_US" },
128 { "nb", "no" },
129 { "zh-Hans", "zh_CN" },
130 { "zh-Hant", "zh_TW" }
0a682745
MS
131};
132#endif /* __APPLE__ */
133
134
135/*
136 * Local functions...
137 */
138
139#ifdef __APPLE__
140static const char *appleLangDefault(void);
0837b7e8
MS
141# ifdef CUPS_BUNDLEDIR
142static cups_array_t *appleMessageLoad(const char *locale);
143# endif /* CUPS_BUNDLEDIR */
0a682745
MS
144#endif /* __APPLE__ */
145static cups_lang_t *cups_cache_lookup(const char *name,
146 cups_encoding_t encoding);
147static int cups_message_compare(_cups_message_t *m1,
148 _cups_message_t *m2);
0837b7e8 149static void cups_message_free(_cups_message_t *m);
0a682745
MS
150static void cups_unquote(char *d, const char *s);
151
152
153#ifdef __APPLE__
154/*
0837b7e8
MS
155 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
156 * locale ID.
0a682745
MS
157 */
158
159const char * /* O - Language ID */
160_cupsAppleLanguage(const char *locale, /* I - Locale ID */
161 char *language,/* I - Language ID buffer */
162 size_t langsize) /* I - Size of language ID buffer */
163{
164 int i; /* Looping var */
165 CFStringRef localeid, /* CF locale identifier */
166 langid; /* CF language identifier */
167
168
169 /*
170 * Copy the locale name and convert, as needed, to the Apple-specific
171 * locale identifier...
172 */
173
174 switch (strlen(locale))
175 {
176 default :
177 /*
178 * Invalid locale...
179 */
180
181 strlcpy(language, "en", langsize);
182 break;
183
184 case 2 :
185 strlcpy(language, locale, langsize);
186 break;
187
188 case 5 :
189 strlcpy(language, locale, langsize);
190
191 if (language[2] == '-')
192 {
193 /*
194 * Convert ll-cc to ll_CC...
195 */
196
197 language[2] = '_';
198 language[3] = toupper(language[3] & 255);
199 language[4] = toupper(language[4] & 255);
200 }
201 break;
202 }
203
204 for (i = 0;
205 i < (int)(sizeof(apple_language_locale) /
206 sizeof(apple_language_locale[0]));
207 i ++)
208 if (!strcmp(locale, apple_language_locale[i].locale))
209 {
210 strlcpy(language, apple_language_locale[i].language, sizeof(language));
211 break;
212 }
213
214 /*
215 * Attempt to map the locale ID to a language ID...
216 */
217
218 if ((localeid = CFStringCreateWithCString(kCFAllocatorDefault, language,
219 kCFStringEncodingASCII)) != NULL)
220 {
221 if ((langid = CFLocaleCreateCanonicalLanguageIdentifierFromString(
222 kCFAllocatorDefault, localeid)) != NULL)
223 {
224 CFStringGetCString(langid, language, langsize, kCFStringEncodingASCII);
225 CFRelease(langid);
226 }
227
228 CFRelease(localeid);
229 }
230
231 /*
232 * Return what we got...
233 */
234
235 return (language);
236}
237#endif /* __APPLE__ */
238
ef416fc2 239
240/*
241 * '_cupsEncodingName()' - Return the character encoding name string
242 * for the given encoding enumeration.
243 */
244
245const char * /* O - Character encoding */
246_cupsEncodingName(
247 cups_encoding_t encoding) /* I - Encoding value */
248{
249 if (encoding < 0 ||
250 encoding >= (sizeof(lang_encodings) / sizeof(const char *)))
c7017ecc
MS
251 {
252 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
253 encoding, lang_encodings[0]));
ef416fc2 254 return (lang_encodings[0]);
c7017ecc 255 }
ef416fc2 256 else
c7017ecc
MS
257 {
258 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
259 encoding, lang_encodings[encoding]));
ef416fc2 260 return (lang_encodings[encoding]);
c7017ecc 261 }
ef416fc2 262}
263
264
265/*
266 * 'cupsLangDefault()' - Return the default language.
267 */
268
269cups_lang_t * /* O - Language data */
270cupsLangDefault(void)
271{
272 return (cupsLangGet(NULL));
273}
274
275
276/*
277 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
278 * for the given language.
279 */
280
281const char * /* O - Character encoding */
282cupsLangEncoding(cups_lang_t *lang) /* I - Language data */
283{
284 if (lang == NULL)
285 return ((char*)lang_encodings[0]);
286 else
287 return ((char*)lang_encodings[lang->encoding]);
288}
289
290
291/*
292 * 'cupsLangFlush()' - Flush all language data out of the cache.
293 */
294
295void
296cupsLangFlush(void)
297{
298 cups_lang_t *lang, /* Current language */
299 *next; /* Next language */
ef416fc2 300
301
302 /*
303 * Free all languages in the cache...
304 */
305
6d2f911b 306 _cupsMutexLock(&lang_mutex);
d6ae789d 307
308 for (lang = lang_cache; lang != NULL; lang = next)
ef416fc2 309 {
310 /*
311 * Free all messages...
312 */
313
314 _cupsMessageFree(lang->strings);
315
316 /*
317 * Then free the language structure itself...
318 */
319
320 next = lang->next;
321 free(lang);
322 }
323
d6ae789d 324 lang_cache = NULL;
325
6d2f911b 326 _cupsMutexUnlock(&lang_mutex);
ef416fc2 327}
328
329
330/*
331 * 'cupsLangFree()' - Free language data.
332 *
5a738aea 333 * This does not actually free anything; use @link cupsLangFlush@ for that.
ef416fc2 334 */
335
336void
337cupsLangFree(cups_lang_t *lang) /* I - Language to free */
338{
6d2f911b 339 _cupsMutexLock(&lang_mutex);
d6ae789d 340
ef416fc2 341 if (lang != NULL && lang->used > 0)
342 lang->used --;
d6ae789d 343
6d2f911b 344 _cupsMutexUnlock(&lang_mutex);
ef416fc2 345}
346
347
348/*
349 * 'cupsLangGet()' - Get a language.
350 */
351
352cups_lang_t * /* O - Language data */
353cupsLangGet(const char *language) /* I - Language or locale */
354{
355 int i; /* Looping var */
480ef0fe 356#ifndef __APPLE__
357 char locale[255]; /* Copy of locale name */
358#endif /* !__APPLE__ */
359 char langname[16], /* Requested language name */
ef416fc2 360 country[16], /* Country code */
361 charset[16], /* Character set */
ef416fc2 362 *csptr, /* Pointer to CODESET string */
ef416fc2 363 *ptr, /* Pointer into language/charset */
0837b7e8 364 real[48]; /* Real language name */
ef416fc2 365 cups_encoding_t encoding; /* Encoding to use */
366 cups_lang_t *lang; /* Current language... */
0837b7e8
MS
367#if !defined(__APPLE__) || !defined(CUPS_BUNDLEDIR)
368 char filename[1024]; /* Filename for language locale file */
ef416fc2 369 _cups_globals_t *cg = _cupsGlobals();
370 /* Pointer to library globals */
0837b7e8 371#endif /* !__APPLE__ || !CUPS_BUNDLEDIR */
ef416fc2 372 static const char * const locale_encodings[] =
373 { /* Locale charset names */
374 "ASCII", "ISO88591", "ISO88592", "ISO88593",
375 "ISO88594", "ISO88595", "ISO88596", "ISO88597",
376 "ISO88598", "ISO88599", "ISO885910", "UTF8",
377 "ISO885913", "ISO885914", "ISO885915", "CP874",
378 "CP1250", "CP1251", "CP1252", "CP1253",
379 "CP1254", "CP1255", "CP1256", "CP1257",
380 "CP1258", "KOI8R", "KOI8U", "ISO885911",
bd7854cb 381 "ISO885916", "MACROMAN", "", "",
ef416fc2 382
383 "", "", "", "",
384 "", "", "", "",
385 "", "", "", "",
386 "", "", "", "",
387 "", "", "", "",
388 "", "", "", "",
389 "", "", "", "",
390 "", "", "", "",
391
392 "CP932", "CP936", "CP949", "CP950",
393 "CP1361", "", "", "",
394 "", "", "", "",
395 "", "", "", "",
396 "", "", "", "",
397 "", "", "", "",
398 "", "", "", "",
399 "", "", "", "",
400
401 "", "", "", "",
402 "", "", "", "",
403 "", "", "", "",
404 "", "", "", "",
405 "", "", "", "",
406 "", "", "", "",
407 "", "", "", "",
408 "", "", "", "",
409
4b3f67ff
MS
410 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
411 "SHIFT_JISX0213"
ef416fc2 412 };
413
414
f11a948a 415 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language));
ef416fc2 416
417#ifdef __APPLE__
757d2cad 418 /*
419 * Set the character set to UTF-8...
420 */
421
422 strcpy(charset, "UTF8");
423
ef416fc2 424 /*
425 * Apple's setlocale doesn't give us the user's localization
426 * preference so we have to look it up this way...
427 */
428
c9fc04c6
MS
429 if (!language)
430 {
b0f6947b 431 if (!getenv("SOFTWARE") || (language = getenv("LANG")) == NULL)
c9fc04c6
MS
432 language = appleLangDefault();
433
f11a948a 434 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language));
c9fc04c6 435 }
757d2cad 436
ef416fc2 437#else
757d2cad 438 /*
439 * Set the charset to "unknown"...
440 */
441
442 charset[0] = '\0';
443
444 /*
445 * Use setlocale() to determine the currently set locale, and then
446 * fallback to environment variables to avoid setting the locale,
447 * since setlocale() is not thread-safe!
448 */
449
450 if (!language)
ef416fc2 451 {
452 /*
453 * First see if the locale has been set; if it is still "C" or
757d2cad 454 * "POSIX", use the environment to get the default...
ef416fc2 455 */
456
457# ifdef LC_MESSAGES
458 ptr = setlocale(LC_MESSAGES, NULL);
459# else
460 ptr = setlocale(LC_ALL, NULL);
461# endif /* LC_MESSAGES */
462
f11a948a 463 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr));
ef416fc2 464
465 if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
ef416fc2 466 {
757d2cad 467 /*
468 * Get the character set from the LC_CTYPE locale setting...
469 */
470
471 if ((ptr = getenv("LC_CTYPE")) == NULL)
472 if ((ptr = getenv("LC_ALL")) == NULL)
473 if ((ptr = getenv("LANG")) == NULL)
474 ptr = "en_US";
475
476 if ((csptr = strchr(ptr, '.')) != NULL)
477 {
478 /*
479 * Extract the character set from the environment...
480 */
481
482 for (ptr = charset, csptr ++; *csptr; csptr ++)
7cf5915e 483 if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr))
757d2cad 484 *ptr++ = *csptr;
485
486 *ptr = '\0';
487 }
757d2cad 488
489 /*
490 * Get the locale for messages from the LC_MESSAGES locale setting...
491 */
492
493 if ((ptr = getenv("LC_MESSAGES")) == NULL)
494 if ((ptr = getenv("LC_ALL")) == NULL)
495 if ((ptr = getenv("LANG")) == NULL)
496 ptr = "en_US";
ef416fc2 497 }
ef416fc2 498
499 if (ptr)
500 {
501 strlcpy(locale, ptr, sizeof(locale));
502 language = locale;
503
a41f09e2
MS
504 /*
505 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
506 */
507
508 if (!strncmp(locale, "nb", 2))
509 locale[1] = 'o';
510
f11a948a 511 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language));
ef416fc2 512 }
513 }
514#endif /* __APPLE__ */
515
516 /*
517 * If "language" is NULL at this point, then chances are we are using
518 * a language that is not installed for the base OS.
519 */
520
521 if (!language)
522 {
523 /*
757d2cad 524 * Switch to the POSIX ("C") locale...
ef416fc2 525 */
526
757d2cad 527 language = "C";
ef416fc2 528 }
529
ef416fc2 530#ifdef CODESET
531 /*
532 * On systems that support the nl_langinfo(CODESET) call, use
533 * this value as the character set...
534 */
535
757d2cad 536 if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
ef416fc2 537 {
538 /*
539 * Copy all of the letters and numbers in the CODESET string...
540 */
541
542 for (ptr = charset; *csptr; csptr ++)
7cf5915e 543 if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1))
ef416fc2 544 *ptr++ = *csptr;
545
546 *ptr = '\0';
547
f11a948a 548 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
e07d4801 549 "nl_langinfo(CODESET)...", charset));
ef416fc2 550 }
551#endif /* CODESET */
552
07725fee 553 /*
554 * If we don't have a character set by now, default to UTF-8...
555 */
556
557 if (!charset[0])
558 strcpy(charset, "UTF8");
559
ef416fc2 560 /*
561 * Parse the language string passed in to a locale string. "C" is the
562 * standard POSIX locale and is copied unchanged. Otherwise the
563 * language string is converted from ll-cc[.charset] (language-country)
564 * to ll_CC[.CHARSET] to match the file naming convention used by all
565 * POSIX-compliant operating systems. Invalid language names are mapped
566 * to the POSIX locale.
567 */
568
569 country[0] = '\0';
570
571 if (language == NULL || !language[0] ||
572 !strcmp(language, "POSIX"))
573 strcpy(langname, "C");
574 else
575 {
576 /*
577 * Copy the parts of the locale string over safely...
578 */
579
580 for (ptr = langname; *language; language ++)
581 if (*language == '_' || *language == '-' || *language == '.')
582 break;
583 else if (ptr < (langname + sizeof(langname) - 1))
584 *ptr++ = tolower(*language & 255);
585
586 *ptr = '\0';
587
588 if (*language == '_' || *language == '-')
589 {
590 /*
591 * Copy the country code...
592 */
593
594 for (language ++, ptr = country; *language; language ++)
595 if (*language == '.')
596 break;
597 else if (ptr < (country + sizeof(country) - 1))
598 *ptr++ = toupper(*language & 255);
599
600 *ptr = '\0';
601 }
602
603 if (*language == '.' && !charset[0])
604 {
605 /*
606 * Copy the encoding...
607 */
608
609 for (language ++, ptr = charset; *language; language ++)
7cf5915e 610 if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1))
ef416fc2 611 *ptr++ = toupper(*language & 255);
612
613 *ptr = '\0';
614 }
615
616 /*
617 * Force a POSIX locale for an invalid language name...
618 */
619
620 if (strlen(langname) != 2)
621 {
622 strcpy(langname, "C");
623 country[0] = '\0';
624 charset[0] = '\0';
625 }
626 }
627
f11a948a 628 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
ef416fc2 629 langname, country, charset));
630
631 /*
632 * Figure out the desired encoding...
633 */
634
635 encoding = CUPS_AUTO_ENCODING;
636
637 if (charset[0])
638 {
bd7854cb 639 for (i = 0;
640 i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
641 i ++)
ef416fc2 642 if (!strcasecmp(charset, locale_encodings[i]))
643 {
644 encoding = (cups_encoding_t)i;
645 break;
646 }
8ca02f3c 647
648 if (encoding == CUPS_AUTO_ENCODING)
649 {
650 /*
651 * Map alternate names for various character sets...
652 */
653
654 if (!strcasecmp(charset, "iso-2022-jp") ||
655 !strcasecmp(charset, "sjis"))
656 encoding = CUPS_WINDOWS_932;
657 else if (!strcasecmp(charset, "iso-2022-cn"))
658 encoding = CUPS_WINDOWS_936;
659 else if (!strcasecmp(charset, "iso-2022-kr"))
660 encoding = CUPS_WINDOWS_949;
661 else if (!strcasecmp(charset, "big5"))
662 encoding = CUPS_WINDOWS_950;
663 }
ef416fc2 664 }
665
f11a948a 666 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding,
ef416fc2 667 encoding == CUPS_AUTO_ENCODING ? "auto" :
668 lang_encodings[encoding]));
669
670 /*
671 * See if we already have this language/country loaded...
672 */
673
bd7854cb 674 if (country[0])
675 {
676 snprintf(real, sizeof(real), "%s_%s", langname, country);
ef416fc2 677
0837b7e8 678#if !defined(__APPLE__) || !defined(CUPS_BUNDLEDIR)
bd7854cb 679 snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
680 real, real);
0837b7e8 681#endif /* !__APPLE__ || !CUPS_BUNDLEDIR */
bd7854cb 682 }
683 else
d6ae789d 684 {
685 strcpy(real, langname);
0837b7e8 686#if !defined(__APPLE__) || !defined(CUPS_BUNDLEDIR)
bd7854cb 687 filename[0] = '\0'; /* anti-compiler-warning-code */
0837b7e8 688#endif /* !__APPLE__ || !CUPS_BUNDLEDIR */
d6ae789d 689 }
690
6d2f911b 691 _cupsMutexLock(&lang_mutex);
d6ae789d 692
c0e1af83 693 if ((lang = cups_cache_lookup(real, encoding)) != NULL)
d6ae789d 694 {
6d2f911b 695 _cupsMutexUnlock(&lang_mutex);
d6ae789d 696
f11a948a 697 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real));
c9fc04c6 698
d6ae789d 699 return (lang);
700 }
ef416fc2 701
0837b7e8 702#if !defined(__APPLE__) || !defined(CUPS_BUNDLEDIR)
ef416fc2 703 if (!country[0] || access(filename, 0))
704 {
705 /*
706 * Country localization not available, look for generic localization...
707 */
708
bd7854cb 709 snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
ef416fc2 710 langname, langname);
711
712 if (access(filename, 0))
713 {
714 /*
715 * No generic localization, so use POSIX...
716 */
717
f11a948a 718 DEBUG_printf(("4cupsLangGet: access(\"%s\", 0): %s", filename,
ae71f5de 719 strerror(errno)));
bd7854cb 720
bd7854cb 721 snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir);
ef416fc2 722 }
ef416fc2 723 }
0837b7e8 724#endif /* !__APPLE__ || !CUPS_BUNDLEDIR */
ef416fc2 725
726 /*
727 * See if there is a free language available; if so, use that
728 * record...
729 */
730
d6ae789d 731 for (lang = lang_cache; lang != NULL; lang = lang->next)
ef416fc2 732 if (lang->used == 0)
733 break;
734
735 if (lang == NULL)
736 {
737 /*
738 * Allocate memory for the language and add it to the cache.
739 */
740
741 if ((lang = calloc(sizeof(cups_lang_t), 1)) == NULL)
d6ae789d 742 {
6d2f911b 743 _cupsMutexUnlock(&lang_mutex);
d6ae789d 744
ef416fc2 745 return (NULL);
d6ae789d 746 }
ef416fc2 747
d6ae789d 748 lang->next = lang_cache;
749 lang_cache = lang;
ef416fc2 750 }
bd7854cb 751 else
752 {
753 /*
754 * Free all old strings as needed...
755 */
ef416fc2 756
bd7854cb 757 _cupsMessageFree(lang->strings);
758 }
ef416fc2 759
760 /*
761 * Then assign the language and encoding fields...
762 */
763
764 lang->used ++;
765 strlcpy(lang->language, real, sizeof(lang->language));
766
767 if (encoding != CUPS_AUTO_ENCODING)
768 lang->encoding = encoding;
769 else
770 lang->encoding = CUPS_UTF8;
771
772 /*
773 * Read the strings from the file...
774 */
775
0837b7e8
MS
776#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
777 lang->strings = appleMessageLoad(lang->language);
778#else
8b116e60 779 lang->strings = _cupsMessageLoad(filename, 1);
0837b7e8 780#endif /* __APPLE__ && CUPS_BUNDLEDIR */
ef416fc2 781
782 /*
783 * Return...
784 */
785
6d2f911b 786 _cupsMutexUnlock(&lang_mutex);
d6ae789d 787
ef416fc2 788 return (lang);
789}
790
791
792/*
793 * '_cupsLangString()' - Get a message string.
794 *
795 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
796 * convert the string to the language encoding.
797 */
798
799const char * /* O - Localized message */
800_cupsLangString(cups_lang_t *lang, /* I - Language */
801 const char *message) /* I - Message */
802{
6d2f911b
MS
803 const char *s; /* Localized message */
804
ef416fc2 805 /*
806 * Range check input...
807 */
808
809 if (!lang || !message)
810 return (message);
811
6d2f911b 812 _cupsMutexLock(&lang_mutex);
d6ae789d 813
6d2f911b 814 s = _cupsMessageLookup(lang->strings, message);
d6ae789d 815
6d2f911b 816 _cupsMutexUnlock(&lang_mutex);
d6ae789d 817
6d2f911b 818 return (s);
ef416fc2 819}
820
821
822/*
823 * '_cupsMessageFree()' - Free a messages array.
824 */
825
826void
827_cupsMessageFree(cups_array_t *a) /* I - Message array */
828{
0837b7e8
MS
829#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
830 /*
831 * Release the cups.strings dictionary as needed...
832 */
ef416fc2 833
0837b7e8
MS
834 if (cupsArrayUserData(a))
835 CFRelease((CFDictionaryRef)cupsArrayUserData(a));
836#endif /* __APPLE__ && CUPS_BUNDLEDIR */
ef416fc2 837
838 /*
839 * Free the array...
840 */
841
842 cupsArrayDelete(a);
843}
844
845
846/*
847 * '_cupsMessageLoad()' - Load a .po file into a messages array.
848 */
849
850cups_array_t * /* O - New message array */
8b116e60 851_cupsMessageLoad(const char *filename, /* I - Message catalog to load */
0837b7e8 852 int unquote) /* I - Unescape \foo in strings? */
ef416fc2 853{
854 cups_file_t *fp; /* Message file */
855 cups_array_t *a; /* Message array */
856 _cups_message_t *m; /* Current message */
857 char s[4096], /* String buffer */
858 *ptr, /* Pointer into buffer */
859 *temp; /* New string */
860 int length; /* Length of combined strings */
861
862
e07d4801 863 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename));
bd7854cb 864
ef416fc2 865 /*
866 * Create an array to hold the messages...
867 */
868
0837b7e8
MS
869 if ((a = cupsArrayNew3((cups_array_func_t)cups_message_compare, NULL,
870 (cups_ahash_func_t)NULL, 0,
871 (cups_acopy_func_t)NULL,
872 (cups_afree_func_t)cups_message_free)) == NULL)
c9fc04c6 873 {
e07d4801 874 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
ef416fc2 875 return (NULL);
c9fc04c6 876 }
ef416fc2 877
878 /*
879 * Open the message catalog file...
880 */
881
882 if ((fp = cupsFileOpen(filename, "r")) == NULL)
c9fc04c6 883 {
e07d4801 884 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
c9fc04c6 885 strerror(errno)));
ef416fc2 886 return (a);
c9fc04c6 887 }
ef416fc2 888
889 /*
890 * Read messages from the catalog file until EOF...
891 *
892 * The format is the GNU gettext .po format, which is fairly simple:
893 *
894 * msgid "some text"
895 * msgstr "localized text"
896 *
bd7854cb 897 * The ID and localized text can span multiple lines using the form:
ef416fc2 898 *
bd7854cb 899 * msgid ""
900 * "some long text"
901 * msgstr ""
902 * "localized text spanning "
ef416fc2 903 * "multiple lines"
904 */
905
906 m = NULL;
907
908 while (cupsFileGets(fp, s, sizeof(s)) != NULL)
909 {
910 /*
911 * Skip blank and comment lines...
912 */
913
914 if (s[0] == '#' || !s[0])
915 continue;
916
917 /*
918 * Strip the trailing quote...
919 */
920
921 if ((ptr = strrchr(s, '\"')) == NULL)
922 continue;
923
924 *ptr = '\0';
925
926 /*
927 * Find start of value...
928 */
929
930 if ((ptr = strchr(s, '\"')) == NULL)
931 continue;
932
933 ptr ++;
934
935 /*
936 * Unquote the text...
937 */
938
8b116e60
MS
939 if (unquote)
940 cups_unquote(ptr, ptr);
ef416fc2 941
942 /*
943 * Create or add to a message...
944 */
945
946 if (!strncmp(s, "msgid", 5))
947 {
bd7854cb 948 /*
949 * Add previous message as needed...
950 */
951
952 if (m)
953 cupsArrayAdd(a, m);
954
955 /*
956 * Create a new message with the given msgid string...
957 */
958
ef416fc2 959 if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
960 {
961 cupsFileClose(fp);
962 return (a);
963 }
964
91c84a35
MS
965 if ((m->id = strdup(ptr)) == NULL)
966 {
967 free(m);
968 cupsFileClose(fp);
969 return (a);
970 }
ef416fc2 971 }
bd7854cb 972 else if (s[0] == '\"' && m)
ef416fc2 973 {
bd7854cb 974 /*
975 * Append to current string...
976 */
ef416fc2 977
b86bc4cf 978 length = (int)strlen(m->str ? m->str : m->id);
ef416fc2 979
bd7854cb 980 if ((temp = realloc(m->str ? m->str : m->id,
981 length + strlen(ptr) + 1)) == NULL)
982 {
983 cupsFileClose(fp);
984 return (a);
985 }
ef416fc2 986
bd7854cb 987 if (m->str)
988 {
ef416fc2 989 /*
bd7854cb 990 * Copy the new portion to the end of the msgstr string - safe
991 * to use strcpy because the buffer is allocated to the correct
992 * size...
ef416fc2 993 */
994
bd7854cb 995 m->str = temp;
996
ef416fc2 997 strcpy(m->str + length, ptr);
998 }
999 else
1000 {
1001 /*
bd7854cb 1002 * Copy the new portion to the end of the msgid string - safe
1003 * to use strcpy because the buffer is allocated to the correct
1004 * size...
ef416fc2 1005 */
1006
bd7854cb 1007 m->id = temp;
1008
1009 strcpy(m->id + length, ptr);
ef416fc2 1010 }
1011 }
bd7854cb 1012 else if (!strncmp(s, "msgstr", 6) && m)
1013 {
1014 /*
1015 * Set the string...
1016 */
1017
91c84a35
MS
1018 if ((m->str = strdup(ptr)) == NULL)
1019 {
1020 cupsFileClose(fp);
1021 return (a);
1022 }
bd7854cb 1023 }
ef416fc2 1024 }
1025
bd7854cb 1026 /*
1027 * Add the last message string to the array as needed...
1028 */
1029
1030 if (m)
1031 cupsArrayAdd(a, m);
1032
ef416fc2 1033 /*
1034 * Close the message catalog file and return the new array...
1035 */
1036
1037 cupsFileClose(fp);
1038
e07d4801 1039 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
c9fc04c6
MS
1040 cupsArrayCount(a)));
1041
ef416fc2 1042 return (a);
1043}
1044
1045
1046/*
1047 * '_cupsMessageLookup()' - Lookup a message string.
1048 */
1049
1050const char * /* O - Localized message */
1051_cupsMessageLookup(cups_array_t *a, /* I - Message array */
1052 const char *m) /* I - Message */
1053{
1054 _cups_message_t key, /* Search key */
1055 *match; /* Matching message */
1056
1057
1058 /*
1059 * Lookup the message string; if it doesn't exist in the catalog,
1060 * then return the message that was passed to us...
1061 */
1062
1063 key.id = (char *)m;
1064 match = (_cups_message_t *)cupsArrayFind(a, &key);
1065
0837b7e8
MS
1066#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1067 if (!match && cupsArrayUserData(a))
1068 {
1069 /*
1070 * Try looking the string up in the cups.strings dictionary...
1071 */
1072
1073 CFDictionaryRef dict; /* cups.strings dictionary */
1074 CFStringRef cfm, /* Message as a CF string */
1075 cfstr; /* Localized text as a CF string */
1076
1077 dict = (CFDictionaryRef)cupsArrayUserData(a);
1078 cfm = CFStringCreateWithCString(kCFAllocatorDefault, m,
1079 kCFStringEncodingUTF8);
1080 match = calloc(1, sizeof(_cups_message_t));
1081 match->id = strdup(m);
1082 cfstr = cfm ? CFDictionaryGetValue(dict, cfm) : NULL;
1083
1084 if (cfstr)
1085 {
1086 char buffer[1024]; /* Message buffer */
1087
1088 CFStringGetCString(cfstr, buffer, sizeof(buffer), kCFStringEncodingUTF8);
1089 match->str = strdup(buffer);
1090 }
1091 else
1092 match->str = strdup(m);
1093
1094 cupsArrayAdd(a, match);
1095 }
1096#endif /* __APPLE__ && CUPS_BUNDLEDIR */
1097
ef416fc2 1098 if (match && match->str)
1099 return (match->str);
1100 else
1101 return (m);
1102}
1103
1104
ef416fc2 1105#ifdef __APPLE__
ef416fc2 1106/*
1107 * 'appleLangDefault()' - Get the default locale string.
1108 */
1109
1110static const char * /* O - Locale string */
1111appleLangDefault(void)
1112{
b94498cf 1113 int i; /* Looping var */
db1f069b
MS
1114 CFBundleRef bundle; /* Main bundle (if any) */
1115 CFArrayRef bundleList; /* List of localizations in bundle */
ef416fc2 1116 CFPropertyListRef localizationList;
1117 /* List of localization data */
1118 CFStringRef languageName; /* Current name */
1119 CFStringRef localeName; /* Canonical from of name */
26d47ec6 1120 char *lang; /* LANG environment variable */
ef416fc2 1121 _cups_globals_t *cg = _cupsGlobals();
1122 /* Pointer to library globals */
1123
1124
b0f6947b
MS
1125 DEBUG_puts("2appleLangDefault()");
1126
ef416fc2 1127 /*
1128 * Only do the lookup and translation the first time.
1129 */
1130
1131 if (!cg->language[0])
1132 {
b0f6947b 1133 if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL)
db1f069b 1134 {
26d47ec6 1135 strlcpy(cg->language, lang, sizeof(cg->language));
db1f069b
MS
1136 return (cg->language);
1137 }
1138 else if ((bundle = CFBundleGetMainBundle()) != NULL &&
1139 (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
ef416fc2 1140 {
26d47ec6 1141 localizationList =
db1f069b 1142 CFBundleCopyPreferredLocalizationsFromArray(bundleList);
26d47ec6 1143
db1f069b
MS
1144 CFRelease(bundleList);
1145 }
1146 else
1147 localizationList =
1148 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1149 kCFPreferencesCurrentApplication);
1150
1151 if (localizationList)
1152 {
1153 if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
1154 CFArrayGetCount(localizationList) > 0)
ef416fc2 1155 {
db1f069b
MS
1156 languageName = CFArrayGetValueAtIndex(localizationList, 0);
1157
1158 if (languageName &&
1159 CFGetTypeID(languageName) == CFStringGetTypeID())
26d47ec6 1160 {
db1f069b
MS
1161 localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(
1162 kCFAllocatorDefault, languageName);
ef416fc2 1163
db1f069b
MS
1164 if (localeName)
1165 {
1166 CFStringGetCString(localeName, cg->language, sizeof(cg->language),
1167 kCFStringEncodingASCII);
1168 CFRelease(localeName);
ef416fc2 1169
e07d4801 1170 DEBUG_printf(("9appleLangDefault: cg->language=\"%s\"",
db1f069b 1171 cg->language));
26d47ec6 1172
db1f069b
MS
1173 /*
1174 * Map new language identifiers to locales...
1175 */
b94498cf 1176
db1f069b 1177 for (i = 0;
0a682745
MS
1178 i < (int)(sizeof(apple_language_locale) /
1179 sizeof(apple_language_locale[0]));
1180 i ++)
db1f069b 1181 {
0a682745 1182 if (!strcmp(cg->language, apple_language_locale[i].language))
b94498cf 1183 {
e07d4801 1184 DEBUG_printf(("9appleLangDefault: mapping \"%s\" to \"%s\"...",
0a682745
MS
1185 cg->language, apple_language_locale[i].locale));
1186 strlcpy(cg->language, apple_language_locale[i].locale,
db1f069b
MS
1187 sizeof(cg->language));
1188 break;
b94498cf 1189 }
db1f069b 1190 }
b94498cf 1191
db1f069b
MS
1192 /*
1193 * Convert language subtag into region subtag...
1194 */
b94498cf 1195
db1f069b
MS
1196 if (cg->language[2] == '-')
1197 cg->language[2] = '_';
b94498cf 1198
db1f069b
MS
1199 if (!strchr(cg->language, '.'))
1200 strlcat(cg->language, ".UTF-8", sizeof(cg->language));
1201 }
26d47ec6 1202 }
26d47ec6 1203 }
db1f069b
MS
1204
1205 CFRelease(localizationList);
ef416fc2 1206 }
1207
1208 /*
1209 * If we didn't find the language, default to en_US...
1210 */
1211
1212 if (!cg->language[0])
1213 strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
1214 }
1215
1216 /*
1217 * Return the cached locale...
1218 */
1219
1220 return (cg->language);
1221}
0837b7e8
MS
1222
1223
1224# ifdef CUPS_BUNDLEDIR
1225/*
1226 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1227 */
1228
1229static cups_array_t * /* O - Message catalog */
1230appleMessageLoad(const char *locale) /* I - Locale ID */
1231{
1232 char filename[1024], /* Path to cups.strings file */
1233 applelang[256]; /* Apple language ID */
1234 CFURLRef url; /* URL to cups.strings file */
1235 CFReadStreamRef stream; /* File */
1236 CFDictionaryRef dict; /* Localization dictionary */
1237
1238 /*
1239 * Load the cups.strings file...
1240 */
1241
1242 snprintf(filename, sizeof(filename),
1243 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings",
1244 _cupsAppleLanguage(locale, applelang, sizeof(applelang)));
1245
1246 url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
1247 (UInt8 *)filename,
1248 strlen(filename), false);
1249 stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
1250 dict = (CFDictionaryRef)CFPropertyListCreateWithStream(
1251 kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, NULL,
1252 NULL);
1253 CFRelease(stream);
1254 CFRelease(url);
1255
1256 /*
1257 * Create and return an empty array to act as a cache for messages, passing the
1258 * dictionary as the user data.
1259 */
1260
1261 return (cupsArrayNew3((cups_array_func_t)cups_message_compare, (void *)dict,
1262 (cups_ahash_func_t)NULL, 0,
1263 (cups_acopy_func_t)NULL,
1264 (cups_afree_func_t)cups_message_free));
1265}
1266# endif /* CUPS_BUNDLEDIR */
ef416fc2 1267#endif /* __APPLE__ */
1268
1269
1270/*
1271 * 'cups_cache_lookup()' - Lookup a language in the cache...
1272 */
1273
1274static cups_lang_t * /* O - Language data or NULL */
0837b7e8
MS
1275cups_cache_lookup(
1276 const char *name, /* I - Name of locale */
1277 cups_encoding_t encoding) /* I - Encoding of locale */
ef416fc2 1278{
1279 cups_lang_t *lang; /* Current language */
1280
1281
e07d4801 1282 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name,
ef416fc2 1283 encoding, encoding == CUPS_AUTO_ENCODING ? "auto" :
1284 lang_encodings[encoding]));
1285
1286 /*
1287 * Loop through the cache and return a match if found...
1288 */
1289
d6ae789d 1290 for (lang = lang_cache; lang != NULL; lang = lang->next)
ef416fc2 1291 {
e07d4801
MS
1292 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1293 "encoding=%d(%s)", lang, lang->language, lang->encoding,
ef416fc2 1294 lang_encodings[lang->encoding]));
1295
1296 if (!strcmp(lang->language, name) &&
1297 (encoding == CUPS_AUTO_ENCODING || encoding == lang->encoding))
1298 {
1299 lang->used ++;
1300
e07d4801 1301 DEBUG_puts("8cups_cache_lookup: returning match!");
ef416fc2 1302
1303 return (lang);
1304 }
1305 }
1306
e07d4801 1307 DEBUG_puts("8cups_cache_lookup: returning NULL!");
ef416fc2 1308
1309 return (NULL);
1310}
1311
1312
1313/*
1314 * 'cups_message_compare()' - Compare two messages.
1315 */
1316
1317static int /* O - Result of comparison */
1318cups_message_compare(
1319 _cups_message_t *m1, /* I - First message */
1320 _cups_message_t *m2) /* I - Second message */
1321{
1322 return (strcmp(m1->id, m2->id));
1323}
1324
1325
0837b7e8
MS
1326/*
1327 * 'cups_message_free()' - Free a message.
1328 */
1329
1330static void
1331cups_message_free(_cups_message_t *m) /* I - Message */
1332{
1333 if (m->id)
1334 free(m->id);
1335
1336 if (m->str)
1337 free(m->str);
1338
1339 free(m);
1340}
1341
1342
ef416fc2 1343/*
1344 * 'cups_unquote()' - Unquote characters in strings...
1345 */
1346
1347static void
1348cups_unquote(char *d, /* O - Unquoted string */
1349 const char *s) /* I - Original string */
1350{
1351 while (*s)
1352 {
1353 if (*s == '\\')
1354 {
1355 s ++;
1356 if (isdigit(*s))
1357 {
1358 *d = 0;
1359
1360 while (isdigit(*s))
1361 {
1362 *d = *d * 8 + *s - '0';
1363 s ++;
1364 }
d09495fa 1365
1366 d ++;
ef416fc2 1367 }
1368 else
1369 {
1370 if (*s == 'n')
1371 *d ++ = '\n';
1372 else if (*s == 'r')
1373 *d ++ = '\r';
1374 else if (*s == 't')
1375 *d ++ = '\t';
1376 else
1377 *d++ = *s;
1378
1379 s ++;
1380 }
1381 }
1382 else
1383 *d++ = *s++;
1384 }
1385
1386 *d = '\0';
1387}
1388
1389
1390/*
75bd9771 1391 * End of "$Id: language.c 7558 2008-05-12 23:46:44Z mike $".
ef416fc2 1392 */