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