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