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