]> git.ipfire.org Git - thirdparty/cups.git/blame - cups/language.c
Merge changes from CUPS 1.4svn-r8492.
[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 *
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",
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
432 DEBUG_printf(("cupsLangGet(language=\"%s\")\n", language ? language : "(null)"));
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
451 DEBUG_printf(("cupsLangGet: language=\"%s\"\n", language));
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
480 DEBUG_printf(("cupsLangGet: current locale is \"%s\"\n",
481 ptr ? ptr : "(null)"));
482
483 if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
ef416fc2 484 {
757d2cad 485 /*
486 * Get the character set from the LC_CTYPE locale setting...
487 */
488
489 if ((ptr = getenv("LC_CTYPE")) == NULL)
490 if ((ptr = getenv("LC_ALL")) == NULL)
491 if ((ptr = getenv("LANG")) == NULL)
492 ptr = "en_US";
493
494 if ((csptr = strchr(ptr, '.')) != NULL)
495 {
496 /*
497 * Extract the character set from the environment...
498 */
499
500 for (ptr = charset, csptr ++; *csptr; csptr ++)
501 if (ptr < (charset + sizeof(charset) - 1) && isalnum(*csptr & 255))
502 *ptr++ = *csptr;
503
504 *ptr = '\0';
505 }
757d2cad 506
507 /*
508 * Get the locale for messages from the LC_MESSAGES locale setting...
509 */
510
511 if ((ptr = getenv("LC_MESSAGES")) == NULL)
512 if ((ptr = getenv("LC_ALL")) == NULL)
513 if ((ptr = getenv("LANG")) == NULL)
514 ptr = "en_US";
ef416fc2 515 }
ef416fc2 516
517 if (ptr)
518 {
519 strlcpy(locale, ptr, sizeof(locale));
520 language = locale;
521
a41f09e2
MS
522 /*
523 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
524 */
525
526 if (!strncmp(locale, "nb", 2))
527 locale[1] = 'o';
528
529 DEBUG_printf(("cupsLangGet: new language value is \"%s\"\n", language));
ef416fc2 530 }
531 }
532#endif /* __APPLE__ */
533
534 /*
535 * If "language" is NULL at this point, then chances are we are using
536 * a language that is not installed for the base OS.
537 */
538
539 if (!language)
540 {
541 /*
757d2cad 542 * Switch to the POSIX ("C") locale...
ef416fc2 543 */
544
757d2cad 545 language = "C";
ef416fc2 546 }
547
ef416fc2 548#ifdef CODESET
549 /*
550 * On systems that support the nl_langinfo(CODESET) call, use
551 * this value as the character set...
552 */
553
757d2cad 554 if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
ef416fc2 555 {
556 /*
557 * Copy all of the letters and numbers in the CODESET string...
558 */
559
560 for (ptr = charset; *csptr; csptr ++)
561 if (isalnum(*csptr & 255) && ptr < (charset + sizeof(charset) - 1))
562 *ptr++ = *csptr;
563
564 *ptr = '\0';
565
566 DEBUG_printf(("cupsLangGet: charset set to \"%s\" via nl_langinfo(CODESET)...\n",
567 charset));
568 }
569#endif /* CODESET */
570
07725fee 571 /*
572 * If we don't have a character set by now, default to UTF-8...
573 */
574
575 if (!charset[0])
576 strcpy(charset, "UTF8");
577
ef416fc2 578 /*
579 * Parse the language string passed in to a locale string. "C" is the
580 * standard POSIX locale and is copied unchanged. Otherwise the
581 * language string is converted from ll-cc[.charset] (language-country)
582 * to ll_CC[.CHARSET] to match the file naming convention used by all
583 * POSIX-compliant operating systems. Invalid language names are mapped
584 * to the POSIX locale.
585 */
586
587 country[0] = '\0';
588
589 if (language == NULL || !language[0] ||
590 !strcmp(language, "POSIX"))
591 strcpy(langname, "C");
592 else
593 {
594 /*
595 * Copy the parts of the locale string over safely...
596 */
597
598 for (ptr = langname; *language; language ++)
599 if (*language == '_' || *language == '-' || *language == '.')
600 break;
601 else if (ptr < (langname + sizeof(langname) - 1))
602 *ptr++ = tolower(*language & 255);
603
604 *ptr = '\0';
605
606 if (*language == '_' || *language == '-')
607 {
608 /*
609 * Copy the country code...
610 */
611
612 for (language ++, ptr = country; *language; language ++)
613 if (*language == '.')
614 break;
615 else if (ptr < (country + sizeof(country) - 1))
616 *ptr++ = toupper(*language & 255);
617
618 *ptr = '\0';
619 }
620
621 if (*language == '.' && !charset[0])
622 {
623 /*
624 * Copy the encoding...
625 */
626
627 for (language ++, ptr = charset; *language; language ++)
628 if (isalnum(*language & 255) && ptr < (charset + sizeof(charset) - 1))
629 *ptr++ = toupper(*language & 255);
630
631 *ptr = '\0';
632 }
633
634 /*
635 * Force a POSIX locale for an invalid language name...
636 */
637
638 if (strlen(langname) != 2)
639 {
640 strcpy(langname, "C");
641 country[0] = '\0';
642 charset[0] = '\0';
643 }
644 }
645
ef416fc2 646 DEBUG_printf(("cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"\n",
647 langname, country, charset));
648
649 /*
650 * Figure out the desired encoding...
651 */
652
653 encoding = CUPS_AUTO_ENCODING;
654
655 if (charset[0])
656 {
bd7854cb 657 for (i = 0;
658 i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
659 i ++)
ef416fc2 660 if (!strcasecmp(charset, locale_encodings[i]))
661 {
662 encoding = (cups_encoding_t)i;
663 break;
664 }
8ca02f3c 665
666 if (encoding == CUPS_AUTO_ENCODING)
667 {
668 /*
669 * Map alternate names for various character sets...
670 */
671
672 if (!strcasecmp(charset, "iso-2022-jp") ||
673 !strcasecmp(charset, "sjis"))
674 encoding = CUPS_WINDOWS_932;
675 else if (!strcasecmp(charset, "iso-2022-cn"))
676 encoding = CUPS_WINDOWS_936;
677 else if (!strcasecmp(charset, "iso-2022-kr"))
678 encoding = CUPS_WINDOWS_949;
679 else if (!strcasecmp(charset, "big5"))
680 encoding = CUPS_WINDOWS_950;
681 }
ef416fc2 682 }
683
684 DEBUG_printf(("cupsLangGet: encoding=%d(%s)\n", encoding,
685 encoding == CUPS_AUTO_ENCODING ? "auto" :
686 lang_encodings[encoding]));
687
688 /*
689 * See if we already have this language/country loaded...
690 */
691
bd7854cb 692 if (country[0])
693 {
694 snprintf(real, sizeof(real), "%s_%s", langname, country);
ef416fc2 695
bd7854cb 696 snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
697 real, real);
698 }
699 else
d6ae789d 700 {
701 strcpy(real, langname);
bd7854cb 702 filename[0] = '\0'; /* anti-compiler-warning-code */
d6ae789d 703 }
704
705#ifdef HAVE_PTHREAD_H
706 pthread_mutex_lock(&lang_mutex);
707#endif /* HAVE_PTHREAD_H */
708
c0e1af83 709 if ((lang = cups_cache_lookup(real, encoding)) != NULL)
d6ae789d 710 {
711#ifdef HAVE_PTHREAD_H
712 pthread_mutex_unlock(&lang_mutex);
713#endif /* HAVE_PTHREAD_H */
714
c9fc04c6
MS
715 DEBUG_printf(("cupsLangGet: Using cached copy of \"%s\"...\n", real));
716
d6ae789d 717 return (lang);
718 }
ef416fc2 719
720 if (!country[0] || access(filename, 0))
721 {
722 /*
723 * Country localization not available, look for generic localization...
724 */
725
bd7854cb 726 snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
ef416fc2 727 langname, langname);
728
729 if (access(filename, 0))
730 {
731 /*
732 * No generic localization, so use POSIX...
733 */
734
ae71f5de
MS
735 DEBUG_printf(("cupsLangGet: access(\"%s\", 0): %s\n", filename,
736 strerror(errno)));
bd7854cb 737
bd7854cb 738 snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir);
ef416fc2 739 }
ef416fc2 740 }
741
742 /*
743 * See if there is a free language available; if so, use that
744 * record...
745 */
746
d6ae789d 747 for (lang = lang_cache; lang != NULL; lang = lang->next)
ef416fc2 748 if (lang->used == 0)
749 break;
750
751 if (lang == NULL)
752 {
753 /*
754 * Allocate memory for the language and add it to the cache.
755 */
756
757 if ((lang = calloc(sizeof(cups_lang_t), 1)) == NULL)
d6ae789d 758 {
759#ifdef HAVE_PTHREAD_H
760 pthread_mutex_unlock(&lang_mutex);
761#endif /* HAVE_PTHREAD_H */
762
ef416fc2 763 return (NULL);
d6ae789d 764 }
ef416fc2 765
d6ae789d 766 lang->next = lang_cache;
767 lang_cache = lang;
ef416fc2 768 }
bd7854cb 769 else
770 {
771 /*
772 * Free all old strings as needed...
773 */
ef416fc2 774
bd7854cb 775 _cupsMessageFree(lang->strings);
776 }
ef416fc2 777
778 /*
779 * Then assign the language and encoding fields...
780 */
781
782 lang->used ++;
783 strlcpy(lang->language, real, sizeof(lang->language));
784
785 if (encoding != CUPS_AUTO_ENCODING)
786 lang->encoding = encoding;
787 else
788 lang->encoding = CUPS_UTF8;
789
790 /*
791 * Read the strings from the file...
792 */
793
8b116e60 794 lang->strings = _cupsMessageLoad(filename, 1);
ef416fc2 795
796 /*
797 * Return...
798 */
799
d6ae789d 800#ifdef HAVE_PTHREAD_H
801 pthread_mutex_unlock(&lang_mutex);
802#endif /* HAVE_PTHREAD_H */
803
ef416fc2 804 return (lang);
805}
806
807
808/*
809 * '_cupsLangString()' - Get a message string.
810 *
811 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
812 * convert the string to the language encoding.
813 */
814
815const char * /* O - Localized message */
816_cupsLangString(cups_lang_t *lang, /* I - Language */
817 const char *message) /* I - Message */
818{
819 /*
820 * Range check input...
821 */
822
823 if (!lang || !message)
824 return (message);
825
d6ae789d 826#ifdef HAVE_PTHREAD_H
827 {
828 const char *s; /* Localized message */
829
830 pthread_mutex_lock(&lang_mutex);
831
832 s = _cupsMessageLookup(lang->strings, message);
833
834 pthread_mutex_unlock(&lang_mutex);
835
836 return (s);
837 }
838#else
ef416fc2 839 return (_cupsMessageLookup(lang->strings, message));
d6ae789d 840#endif /* HAVE_PTHREAD_H */
ef416fc2 841}
842
843
844/*
845 * '_cupsMessageFree()' - Free a messages array.
846 */
847
848void
849_cupsMessageFree(cups_array_t *a) /* I - Message array */
850{
851 _cups_message_t *m; /* Current message */
852
853
854 for (m = (_cups_message_t *)cupsArrayFirst(a);
855 m;
856 m = (_cups_message_t *)cupsArrayNext(a))
857 {
858 /*
859 * Remove the message from the array, then free the message and strings.
860 */
861
862 cupsArrayRemove(a, m);
863
864 if (m->id)
865 free(m->id);
866
867 if (m->str)
868 free(m->str);
869
870 free(m);
871 }
872
873 /*
874 * Free the array...
875 */
876
877 cupsArrayDelete(a);
878}
879
880
881/*
882 * '_cupsMessageLoad()' - Load a .po file into a messages array.
883 */
884
885cups_array_t * /* O - New message array */
8b116e60
MS
886_cupsMessageLoad(const char *filename, /* I - Message catalog to load */
887 int unquote) /* I - Unescape \foo in strings */
ef416fc2 888{
889 cups_file_t *fp; /* Message file */
890 cups_array_t *a; /* Message array */
891 _cups_message_t *m; /* Current message */
892 char s[4096], /* String buffer */
893 *ptr, /* Pointer into buffer */
894 *temp; /* New string */
895 int length; /* Length of combined strings */
896
897
bd7854cb 898 DEBUG_printf(("_cupsMessageLoad(filename=\"%s\")\n", filename));
899
ef416fc2 900 /*
901 * Create an array to hold the messages...
902 */
903
904 if ((a = cupsArrayNew((cups_array_func_t)cups_message_compare, NULL)) == NULL)
c9fc04c6
MS
905 {
906 DEBUG_puts("_cupsMessageLoad: Unable to allocate array!");
ef416fc2 907 return (NULL);
c9fc04c6 908 }
ef416fc2 909
910 /*
911 * Open the message catalog file...
912 */
913
914 if ((fp = cupsFileOpen(filename, "r")) == NULL)
c9fc04c6
MS
915 {
916 DEBUG_printf(("_cupsMessageLoad: Unable to open file: %s\n",
917 strerror(errno)));
ef416fc2 918 return (a);
c9fc04c6 919 }
ef416fc2 920
921 /*
922 * Read messages from the catalog file until EOF...
923 *
924 * The format is the GNU gettext .po format, which is fairly simple:
925 *
926 * msgid "some text"
927 * msgstr "localized text"
928 *
bd7854cb 929 * The ID and localized text can span multiple lines using the form:
ef416fc2 930 *
bd7854cb 931 * msgid ""
932 * "some long text"
933 * msgstr ""
934 * "localized text spanning "
ef416fc2 935 * "multiple lines"
936 */
937
938 m = NULL;
939
940 while (cupsFileGets(fp, s, sizeof(s)) != NULL)
941 {
942 /*
943 * Skip blank and comment lines...
944 */
945
946 if (s[0] == '#' || !s[0])
947 continue;
948
949 /*
950 * Strip the trailing quote...
951 */
952
953 if ((ptr = strrchr(s, '\"')) == NULL)
954 continue;
955
956 *ptr = '\0';
957
958 /*
959 * Find start of value...
960 */
961
962 if ((ptr = strchr(s, '\"')) == NULL)
963 continue;
964
965 ptr ++;
966
967 /*
968 * Unquote the text...
969 */
970
8b116e60
MS
971 if (unquote)
972 cups_unquote(ptr, ptr);
ef416fc2 973
974 /*
975 * Create or add to a message...
976 */
977
978 if (!strncmp(s, "msgid", 5))
979 {
bd7854cb 980 /*
981 * Add previous message as needed...
982 */
983
984 if (m)
985 cupsArrayAdd(a, m);
986
987 /*
988 * Create a new message with the given msgid string...
989 */
990
ef416fc2 991 if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
992 {
993 cupsFileClose(fp);
994 return (a);
995 }
996
91c84a35
MS
997 if ((m->id = strdup(ptr)) == NULL)
998 {
999 free(m);
1000 cupsFileClose(fp);
1001 return (a);
1002 }
ef416fc2 1003 }
bd7854cb 1004 else if (s[0] == '\"' && m)
ef416fc2 1005 {
bd7854cb 1006 /*
1007 * Append to current string...
1008 */
ef416fc2 1009
b86bc4cf 1010 length = (int)strlen(m->str ? m->str : m->id);
ef416fc2 1011
bd7854cb 1012 if ((temp = realloc(m->str ? m->str : m->id,
1013 length + strlen(ptr) + 1)) == NULL)
1014 {
1015 cupsFileClose(fp);
1016 return (a);
1017 }
ef416fc2 1018
bd7854cb 1019 if (m->str)
1020 {
ef416fc2 1021 /*
bd7854cb 1022 * Copy the new portion to the end of the msgstr string - safe
1023 * to use strcpy because the buffer is allocated to the correct
1024 * size...
ef416fc2 1025 */
1026
bd7854cb 1027 m->str = temp;
1028
ef416fc2 1029 strcpy(m->str + length, ptr);
1030 }
1031 else
1032 {
1033 /*
bd7854cb 1034 * Copy the new portion to the end of the msgid string - safe
1035 * to use strcpy because the buffer is allocated to the correct
1036 * size...
ef416fc2 1037 */
1038
bd7854cb 1039 m->id = temp;
1040
1041 strcpy(m->id + length, ptr);
ef416fc2 1042 }
1043 }
bd7854cb 1044 else if (!strncmp(s, "msgstr", 6) && m)
1045 {
1046 /*
1047 * Set the string...
1048 */
1049
91c84a35
MS
1050 if ((m->str = strdup(ptr)) == NULL)
1051 {
1052 cupsFileClose(fp);
1053 return (a);
1054 }
bd7854cb 1055 }
ef416fc2 1056 }
1057
bd7854cb 1058 /*
1059 * Add the last message string to the array as needed...
1060 */
1061
1062 if (m)
1063 cupsArrayAdd(a, m);
1064
ef416fc2 1065 /*
1066 * Close the message catalog file and return the new array...
1067 */
1068
1069 cupsFileClose(fp);
1070
c9fc04c6
MS
1071 DEBUG_printf(("_cupsMessageLoad: Returning %d messages...\n",
1072 cupsArrayCount(a)));
1073
ef416fc2 1074 return (a);
1075}
1076
1077
1078/*
1079 * '_cupsMessageLookup()' - Lookup a message string.
1080 */
1081
1082const char * /* O - Localized message */
1083_cupsMessageLookup(cups_array_t *a, /* I - Message array */
1084 const char *m) /* I - Message */
1085{
1086 _cups_message_t key, /* Search key */
1087 *match; /* Matching message */
1088
1089
1090 /*
1091 * Lookup the message string; if it doesn't exist in the catalog,
1092 * then return the message that was passed to us...
1093 */
1094
1095 key.id = (char *)m;
1096 match = (_cups_message_t *)cupsArrayFind(a, &key);
1097
1098 if (match && match->str)
1099 return (match->str);
1100 else
1101 return (m);
1102}
1103
1104
ef416fc2 1105#ifdef __APPLE__
ef416fc2 1106/*
1107 * 'appleLangDefault()' - Get the default locale string.
1108 */
1109
1110static const char * /* O - Locale string */
1111appleLangDefault(void)
1112{
b94498cf 1113 int i; /* Looping var */
db1f069b
MS
1114 CFBundleRef bundle; /* Main bundle (if any) */
1115 CFArrayRef bundleList; /* List of localizations in bundle */
ef416fc2 1116 CFPropertyListRef localizationList;
1117 /* List of localization data */
1118 CFStringRef languageName; /* Current name */
1119 CFStringRef localeName; /* Canonical from of name */
26d47ec6 1120 char *lang; /* LANG environment variable */
ef416fc2 1121 _cups_globals_t *cg = _cupsGlobals();
1122 /* Pointer to library globals */
1123
1124
1125 /*
1126 * Only do the lookup and translation the first time.
1127 */
1128
1129 if (!cg->language[0])
1130 {
26d47ec6 1131 if ((lang = getenv("LANG")))
db1f069b 1132 {
26d47ec6 1133 strlcpy(cg->language, lang, sizeof(cg->language));
db1f069b
MS
1134 return (cg->language);
1135 }
1136 else if ((bundle = CFBundleGetMainBundle()) != NULL &&
1137 (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
ef416fc2 1138 {
26d47ec6 1139 localizationList =
db1f069b 1140 CFBundleCopyPreferredLocalizationsFromArray(bundleList);
26d47ec6 1141
db1f069b
MS
1142 CFRelease(bundleList);
1143 }
1144 else
1145 localizationList =
1146 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1147 kCFPreferencesCurrentApplication);
1148
1149 if (localizationList)
1150 {
1151 if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
1152 CFArrayGetCount(localizationList) > 0)
ef416fc2 1153 {
db1f069b
MS
1154 languageName = CFArrayGetValueAtIndex(localizationList, 0);
1155
1156 if (languageName &&
1157 CFGetTypeID(languageName) == CFStringGetTypeID())
26d47ec6 1158 {
db1f069b
MS
1159 localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(
1160 kCFAllocatorDefault, languageName);
ef416fc2 1161
db1f069b
MS
1162 if (localeName)
1163 {
1164 CFStringGetCString(localeName, cg->language, sizeof(cg->language),
1165 kCFStringEncodingASCII);
1166 CFRelease(localeName);
ef416fc2 1167
db1f069b
MS
1168 DEBUG_printf(("appleLangDefault: cg->language=\"%s\"\n",
1169 cg->language));
26d47ec6 1170
db1f069b
MS
1171 /*
1172 * Map new language identifiers to locales...
1173 */
b94498cf 1174
db1f069b 1175 for (i = 0;
0a682745
MS
1176 i < (int)(sizeof(apple_language_locale) /
1177 sizeof(apple_language_locale[0]));
1178 i ++)
db1f069b 1179 {
0a682745 1180 if (!strcmp(cg->language, apple_language_locale[i].language))
b94498cf 1181 {
db1f069b 1182 DEBUG_printf(("appleLangDefault: mapping \"%s\" to \"%s\"...\n",
0a682745
MS
1183 cg->language, apple_language_locale[i].locale));
1184 strlcpy(cg->language, apple_language_locale[i].locale,
db1f069b
MS
1185 sizeof(cg->language));
1186 break;
b94498cf 1187 }
db1f069b 1188 }
b94498cf 1189
db1f069b
MS
1190 /*
1191 * Convert language subtag into region subtag...
1192 */
b94498cf 1193
db1f069b
MS
1194 if (cg->language[2] == '-')
1195 cg->language[2] = '_';
b94498cf 1196
db1f069b
MS
1197 if (!strchr(cg->language, '.'))
1198 strlcat(cg->language, ".UTF-8", sizeof(cg->language));
1199 }
26d47ec6 1200 }
26d47ec6 1201 }
db1f069b
MS
1202
1203 CFRelease(localizationList);
ef416fc2 1204 }
1205
1206 /*
1207 * If we didn't find the language, default to en_US...
1208 */
1209
1210 if (!cg->language[0])
1211 strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
1212 }
1213
1214 /*
1215 * Return the cached locale...
1216 */
1217
1218 return (cg->language);
1219}
ef416fc2 1220#endif /* __APPLE__ */
1221
1222
1223/*
1224 * 'cups_cache_lookup()' - Lookup a language in the cache...
1225 */
1226
1227static cups_lang_t * /* O - Language data or NULL */
1228cups_cache_lookup(const char *name,/* I - Name of locale */
1229 cups_encoding_t encoding)
1230 /* I - Encoding of locale */
1231{
1232 cups_lang_t *lang; /* Current language */
1233
1234
1235 DEBUG_printf(("cups_cache_lookup(name=\"%s\", encoding=%d(%s))\n", name,
1236 encoding, encoding == CUPS_AUTO_ENCODING ? "auto" :
1237 lang_encodings[encoding]));
1238
1239 /*
1240 * Loop through the cache and return a match if found...
1241 */
1242
d6ae789d 1243 for (lang = lang_cache; lang != NULL; lang = lang->next)
ef416fc2 1244 {
1245 DEBUG_printf(("cups_cache_lookup: lang=%p, language=\"%s\", encoding=%d(%s)\n",
1246 lang, lang->language, lang->encoding,
1247 lang_encodings[lang->encoding]));
1248
1249 if (!strcmp(lang->language, name) &&
1250 (encoding == CUPS_AUTO_ENCODING || encoding == lang->encoding))
1251 {
1252 lang->used ++;
1253
1254 DEBUG_puts("cups_cache_lookup: returning match!");
1255
1256 return (lang);
1257 }
1258 }
1259
1260 DEBUG_puts("cups_cache_lookup: returning NULL!");
1261
1262 return (NULL);
1263}
1264
1265
1266/*
1267 * 'cups_message_compare()' - Compare two messages.
1268 */
1269
1270static int /* O - Result of comparison */
1271cups_message_compare(
1272 _cups_message_t *m1, /* I - First message */
1273 _cups_message_t *m2) /* I - Second message */
1274{
1275 return (strcmp(m1->id, m2->id));
1276}
1277
1278
1279/*
1280 * 'cups_unquote()' - Unquote characters in strings...
1281 */
1282
1283static void
1284cups_unquote(char *d, /* O - Unquoted string */
1285 const char *s) /* I - Original string */
1286{
1287 while (*s)
1288 {
1289 if (*s == '\\')
1290 {
1291 s ++;
1292 if (isdigit(*s))
1293 {
1294 *d = 0;
1295
1296 while (isdigit(*s))
1297 {
1298 *d = *d * 8 + *s - '0';
1299 s ++;
1300 }
d09495fa 1301
1302 d ++;
ef416fc2 1303 }
1304 else
1305 {
1306 if (*s == 'n')
1307 *d ++ = '\n';
1308 else if (*s == 'r')
1309 *d ++ = '\r';
1310 else if (*s == 't')
1311 *d ++ = '\t';
1312 else
1313 *d++ = *s;
1314
1315 s ++;
1316 }
1317 }
1318 else
1319 *d++ = *s++;
1320 }
1321
1322 *d = '\0';
1323}
1324
1325
1326/*
75bd9771 1327 * End of "$Id: language.c 7558 2008-05-12 23:46:44Z mike $".
ef416fc2 1328 */