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