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