1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * Copyright 2008  Red Hat, Inc,
4  *           2007  William Jon McCann <mccann@jhu.edu>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  *
19  * Written by : William Jon McCann <mccann@jhu.edu>
20  *              Ray Strode <rstrode@redhat.com>
21  */
22 
23 #include "config.h"
24 
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <dirent.h>
31 #include <locale.h>
32 #include <langinfo.h>
33 #include <sys/stat.h>
34 
35 #include <glib.h>
36 #include <glib/gi18n.h>
37 #include <glib/gstdio.h>
38 
39 #define GNOME_DESKTOP_USE_UNSTABLE_API
40 #include "gnome-languages.h"
41 
42 #include <langinfo.h>
43 #ifndef __LC_LAST
44 #define __LC_LAST       13
45 #endif
46 
47 #define ISO_CODES_DATADIR ISO_CODES_PREFIX "/share/xml/iso-codes"
48 #define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX "/share/locale"
49 
50 #if 0
51 #include "default-input-sources.h"
52 #endif
53 
54 typedef struct _GnomeLocale {
55         char *id;
56         char *name;
57         char *language_code;
58         char *territory_code;
59         char *codeset;
60         char *modifier;
61 } GnomeLocale;
62 
63 static GHashTable *gnome_languages_map;
64 static GHashTable *gnome_territories_map;
65 static GHashTable *gnome_available_locales_map;
66 static GHashTable *gnome_language_count_map;
67 static GHashTable *gnome_territory_count_map;
68 
69 static char * construct_language_name (const char *language,
70                                        const char *territory,
71                                        const char *codeset,
72                                        const char *modifier);
73 
74 static gboolean language_name_is_valid (const char *language_name);
75 
76 static void
gnome_locale_free(GnomeLocale * locale)77 gnome_locale_free (GnomeLocale *locale)
78 {
79         if (locale == NULL) {
80                 return;
81         }
82 
83         g_free (locale->id);
84         g_free (locale->name);
85         g_free (locale->codeset);
86         g_free (locale->modifier);
87         g_free (locale->language_code);
88         g_free (locale->territory_code);
89         g_free (locale);
90 }
91 
92 static char *
normalize_codeset(const char * codeset)93 normalize_codeset (const char *codeset)
94 {
95         if (codeset == NULL)
96                 return NULL;
97 
98         if (g_str_equal (codeset, "UTF-8") ||
99             g_str_equal (codeset, "utf8"))
100                 return g_strdup ("UTF-8");
101 
102         return g_strdup (codeset);
103 }
104 
105 /**
106  * gnome_parse_locale:
107  * @locale: a locale string
108  * @language_codep: (out) (allow-none) (transfer full): location to
109  * store the language code, or %NULL
110  * @country_codep: (out) (allow-none) (transfer full): location to
111  * store the country code, or %NULL
112  * @codesetp: (out) (allow-none) (transfer full): location to
113  * store the codeset, or %NULL
114  * @modifierp: (out) (allow-none) (transfer full): location to
115  * store the modifier, or %NULL
116  *
117  * Extracts the various components of a locale string of the form
118  * [language[_country][.codeset][@modifier]]. See
119  * http://en.wikipedia.org/wiki/Locale.
120  *
121  * Return value: %TRUE if parsing was successful.
122  *
123  * Since: 3.8
124  */
125 gboolean
gnome_parse_locale(const char * locale,char ** language_codep,char ** country_codep,char ** codesetp,char ** modifierp)126 gnome_parse_locale (const char *locale,
127                     char      **language_codep,
128                     char      **country_codep,
129                     char      **codesetp,
130                     char      **modifierp)
131 {
132         static GRegex *re = NULL;
133         GMatchInfo *match_info;
134         gboolean    res;
135         gboolean    retval;
136 
137         match_info = NULL;
138         retval = FALSE;
139 
140         if (re == NULL) {
141                 GError *error = NULL;
142                 re = g_regex_new ("^(?P<language>[^_.@[:space:]]+)"
143                                   "(_(?P<territory>[[:upper:]]+))?"
144                                   "(\\.(?P<codeset>[-_0-9a-zA-Z]+))?"
145                                   "(@(?P<modifier>[[:ascii:]]+))?$",
146                                   0, 0, &error);
147                 if (re == NULL) {
148                         g_warning ("%s", error->message);
149                         g_error_free (error);
150                         goto out;
151                 }
152         }
153 
154         if (!g_regex_match (re, locale, 0, &match_info) ||
155             g_match_info_is_partial_match (match_info)) {
156                 g_warning ("locale '%s' isn't valid\n", locale);
157                 goto out;
158         }
159 
160         res = g_match_info_matches (match_info);
161         if (! res) {
162                 g_warning ("Unable to parse locale: %s", locale);
163                 goto out;
164         }
165 
166         retval = TRUE;
167 
168         if (language_codep != NULL) {
169                 *language_codep = g_match_info_fetch_named (match_info, "language");
170         }
171 
172         if (country_codep != NULL) {
173                 *country_codep = g_match_info_fetch_named (match_info, "territory");
174 
175                 if (*country_codep != NULL &&
176                     *country_codep[0] == '\0') {
177                         g_free (*country_codep);
178                         *country_codep = NULL;
179                 }
180         }
181 
182         if (codesetp != NULL) {
183                 *codesetp = g_match_info_fetch_named (match_info, "codeset");
184 
185                 if (*codesetp != NULL &&
186                     *codesetp[0] == '\0') {
187                         g_free (*codesetp);
188                         *codesetp = NULL;
189                 }
190         }
191 
192         if (modifierp != NULL) {
193                 *modifierp = g_match_info_fetch_named (match_info, "modifier");
194 
195                 if (*modifierp != NULL &&
196                     *modifierp[0] == '\0') {
197                         g_free (*modifierp);
198                         *modifierp = NULL;
199                 }
200         }
201 
202         if (codesetp != NULL && *codesetp != NULL) {
203                 g_autofree gchar *normalized_codeset = NULL;
204                 g_autofree gchar *normalized_name = NULL;
205 
206                 normalized_codeset = normalize_codeset (*codesetp);
207                 normalized_name = construct_language_name (language_codep ? *language_codep : NULL,
208                                                            country_codep ? *country_codep : NULL,
209                                                            normalized_codeset,
210                                                            modifierp ? *modifierp : NULL);
211 
212                 if (language_name_is_valid (normalized_name)) {
213                         g_free (*codesetp);
214                         *codesetp = g_steal_pointer (&normalized_codeset);
215                 }
216         }
217 
218  out:
219         g_match_info_free (match_info);
220 
221         return retval;
222 }
223 
224 static char *
construct_language_name(const char * language,const char * territory,const char * codeset,const char * modifier)225 construct_language_name (const char *language,
226                          const char *territory,
227                          const char *codeset,
228                          const char *modifier)
229 {
230         char *name;
231 
232         g_assert (language != NULL && language[0] != 0);
233         g_assert (territory == NULL || territory[0] != 0);
234         g_assert (codeset == NULL || codeset[0] != 0);
235         g_assert (modifier == NULL || modifier[0] != 0);
236 
237         name = g_strdup_printf ("%s%s%s%s%s%s%s",
238                                 language,
239                                 territory != NULL? "_" : "",
240                                 territory != NULL? territory : "",
241                                 codeset != NULL? "." : "",
242                                 codeset != NULL? codeset : "",
243                                 modifier != NULL? "@" : "",
244                                 modifier != NULL? modifier : "");
245 
246         return name;
247 }
248 
249 /**
250  * gnome_normalize_locale:
251  * @locale: a locale string
252  *
253  * Gets the normalized locale string in the form
254  * [language[_country][.codeset][@modifier]] for @name.
255  *
256  * Return value: (transfer full): normalized locale string. Caller
257  * takes ownership.
258  *
259  * Since: 3.8
260  */
261 char *
gnome_normalize_locale(const char * locale)262 gnome_normalize_locale (const char *locale)
263 {
264         char *normalized_name;
265         gboolean valid;
266         g_autofree char *language_code = NULL;
267         g_autofree char *territory_code = NULL;
268         g_autofree char *codeset = NULL;
269         g_autofree char *modifier = NULL;
270 
271         if (locale[0] == '\0') {
272                 return NULL;
273         }
274 
275         valid = gnome_parse_locale (locale,
276                                     &language_code,
277                                     &territory_code,
278                                     &codeset, &modifier);
279         if (!valid)
280                 return NULL;
281 
282         normalized_name = construct_language_name (language_code,
283                                                    territory_code,
284                                                    codeset, modifier);
285         return normalized_name;
286 }
287 
288 static gboolean
language_name_is_valid(const char * language_name)289 language_name_is_valid (const char *language_name)
290 {
291         locale_t locale;
292 
293         locale = newlocale (LC_MESSAGES_MASK, language_name, (locale_t) 0);
294         if (locale != (locale_t) 0) {
295                 freelocale (locale);
296                 return TRUE;
297         }
298 
299         return FALSE;
300 }
301 
302 static void
language_name_get_codeset_details(const char * language_name,char ** pcodeset,gboolean * is_utf8)303 language_name_get_codeset_details (const char  *language_name,
304                                    char       **pcodeset,
305                                    gboolean    *is_utf8)
306 {
307         locale_t locale;
308         locale_t old_locale;
309         const char *codeset = NULL;
310 
311         locale = newlocale (LC_CTYPE_MASK, language_name, (locale_t) 0);
312         if (locale == (locale_t) 0)
313                 return;
314 
315         old_locale = uselocale (locale);
316 
317         codeset = nl_langinfo (CODESET);
318 
319         if (pcodeset != NULL) {
320                 *pcodeset = g_strdup (codeset);
321         }
322 
323         if (is_utf8 != NULL) {
324                 g_autofree char *normalized_codeset = normalize_codeset (codeset);
325 
326                 *is_utf8 = strcmp (normalized_codeset, "UTF-8") == 0;
327         }
328 
329         uselocale (old_locale);
330         freelocale (locale);
331 }
332 
333 /**
334  * gnome_language_has_translations:
335  * @code: an ISO 639 code string
336  *
337  * Returns %TRUE if there are translations for language @code.
338  *
339  * Return value: %TRUE if there are translations for language @code.
340  *
341  * Since: 3.8
342  */
343 gboolean
gnome_language_has_translations(const char * code)344 gnome_language_has_translations (const char *code)
345 {
346         GDir        *dir;
347         const char  *name;
348         gboolean     has_translations;
349         g_autofree char *path = NULL;
350 
351         path = g_build_filename (LOCALEDIR, code, "LC_MESSAGES", NULL);
352 
353         has_translations = FALSE;
354         dir = g_dir_open (path, 0, NULL);
355 
356         if (dir == NULL) {
357                 goto out;
358         }
359 
360         do {
361                 name = g_dir_read_name (dir);
362 
363                 if (name == NULL) {
364                         break;
365                 }
366 
367                 if (g_str_has_suffix (name, ".mo")) {
368                         has_translations = TRUE;
369                         break;
370                 }
371         } while (name != NULL);
372         g_dir_close (dir);
373 
374  out:
375         return has_translations;
376 }
377 
378 static gboolean
add_locale(const char * language_name,gboolean utf8_only)379 add_locale (const char *language_name,
380             gboolean    utf8_only)
381 {
382         GnomeLocale *locale;
383         GnomeLocale *old_locale;
384         g_autofree char *name = NULL;
385         gboolean   is_utf8 = FALSE;
386         gboolean   valid = FALSE;
387 
388         g_return_val_if_fail (language_name != NULL, FALSE);
389         g_return_val_if_fail (*language_name != '\0', FALSE);
390 
391         language_name_get_codeset_details (language_name, NULL, &is_utf8);
392 
393         if (is_utf8) {
394                 name = g_strdup (language_name);
395         } else if (utf8_only) {
396 
397                 if (strchr (language_name, '.'))
398                         return FALSE;
399 
400                 /* If the locale name has no dot, assume that its
401                  * encoding part is missing and try again after adding
402                  * ".UTF-8". This catches locale names like "de_DE".
403                  */
404                 name = g_strdup_printf ("%s.UTF-8", language_name);
405 
406                 language_name_get_codeset_details (name, NULL, &is_utf8);
407                 if (!is_utf8)
408                         return FALSE;
409         } else {
410                 name = g_strdup (language_name);
411         }
412 
413         if (!language_name_is_valid (name)) {
414                 g_debug ("Ignoring '%s' as a locale, since it's invalid", name);
415                 return FALSE;
416         }
417 
418         locale = g_new0 (GnomeLocale, 1);
419         valid = gnome_parse_locale (name,
420                                     &locale->language_code,
421                                     &locale->territory_code,
422                                     &locale->codeset,
423                                     &locale->modifier);
424         if (!valid) {
425                 gnome_locale_free (locale);
426                 return FALSE;
427         }
428 
429         locale->id = construct_language_name (locale->language_code, locale->territory_code,
430                                               NULL, locale->modifier);
431         locale->name = construct_language_name (locale->language_code, locale->territory_code,
432                                                 locale->codeset, locale->modifier);
433 
434         if (!gnome_language_has_translations (locale->name) &&
435             !gnome_language_has_translations (locale->id) &&
436             !gnome_language_has_translations (locale->language_code) &&
437             utf8_only) {
438                 g_debug ("Ignoring '%s' as a locale, since it lacks translations", locale->name);
439                 gnome_locale_free (locale);
440                 return FALSE;
441         }
442 
443         if (!utf8_only) {
444                 g_free (locale->id);
445                 locale->id = g_strdup (locale->name);
446         }
447 
448         old_locale = g_hash_table_lookup (gnome_available_locales_map, locale->id);
449         if (old_locale != NULL) {
450                 if (strlen (old_locale->name) > strlen (locale->name)) {
451                         gnome_locale_free (locale);
452                         return FALSE;
453                 }
454         }
455 
456         g_hash_table_insert (gnome_available_locales_map, g_strdup (locale->id), locale);
457 
458         return TRUE;
459 }
460 
461 static int
select_dirs(const struct dirent * dirent)462 select_dirs (const struct dirent *dirent)
463 {
464         int result = 0;
465 
466         if (strcmp (dirent->d_name, ".") != 0 && strcmp (dirent->d_name, "..") != 0) {
467                 mode_t mode = 0;
468 
469 #ifndef DTTOIF
470 #define DTTOIF(dirtype) ((dirtype) << 12)
471 #endif
472 
473 #ifdef _DIRENT_HAVE_D_TYPE
474                 if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK) {
475                         mode = DTTOIF (dirent->d_type);
476                 } else
477 #endif
478                         {
479                                 struct stat st;
480                                 g_autofree char *path = NULL;
481 
482                                 path = g_build_filename (LIBLOCALEDIR, dirent->d_name, NULL);
483                                 if (g_stat (path, &st) == 0) {
484                                         mode = st.st_mode;
485                                 }
486                         }
487 
488                 result = S_ISDIR (mode);
489         }
490 
491         return result;
492 }
493 
494 static gboolean
collect_locales_from_directory(void)495 collect_locales_from_directory (void)
496 {
497         gboolean found_locales = FALSE;
498         struct dirent **dirents;
499         int             ndirents;
500         int             cnt;
501 
502         ndirents = scandir (LIBLOCALEDIR, &dirents, select_dirs, alphasort);
503 
504         for (cnt = 0; cnt < ndirents; ++cnt) {
505                 if (add_locale (dirents[cnt]->d_name, TRUE))
506                         found_locales = TRUE;
507         }
508 
509         if (ndirents > 0) {
510                 free (dirents);
511         }
512         return found_locales;
513 }
514 
515 static gboolean
collect_locales_from_localebin(void)516 collect_locales_from_localebin (void)
517 {
518         gboolean found_locales = FALSE;
519         const gchar *argv[] = { "locale", "-a", NULL };
520         gchar **linep;
521         g_auto (GStrv) lines = NULL;
522         g_autofree gchar *output = NULL;
523 
524         if (g_spawn_sync (NULL, (gchar **) argv, NULL,
525                           G_SPAWN_SEARCH_PATH|G_SPAWN_STDERR_TO_DEV_NULL,
526                           NULL, NULL, &output, NULL, NULL, NULL) == FALSE)
527                 return FALSE;
528 
529         g_return_val_if_fail (output != NULL, FALSE);
530 
531         lines = g_strsplit (output, "\n", 0);
532         if (lines) {
533                 linep = lines;
534                 while (*linep) {
535                         if (*linep[0] && add_locale (*linep, TRUE))
536                                 found_locales = TRUE;
537                         linep++;
538                 }
539         }
540 
541         return found_locales;
542 }
543 
544 static void
count_languages_and_territories(void)545 count_languages_and_territories (void)
546 {
547 	gpointer value;
548 	GHashTableIter iter;
549 
550 	gnome_language_count_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
551 	gnome_territory_count_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
552 
553         g_hash_table_iter_init (&iter, gnome_available_locales_map);
554         while (g_hash_table_iter_next (&iter, NULL, &value)) {
555                 GnomeLocale *locale;
556 
557                 locale = (GnomeLocale *) value;
558 
559 		if (locale->language_code != NULL) {
560 			int count;
561 
562 			count = GPOINTER_TO_INT (g_hash_table_lookup (gnome_language_count_map, locale->language_code));
563 			count++;
564 			g_hash_table_insert (gnome_language_count_map, g_strdup (locale->language_code), GINT_TO_POINTER (count));
565 		}
566 
567 		if (locale->territory_code != NULL) {
568 			int count;
569 
570 			count = GPOINTER_TO_INT (g_hash_table_lookup (gnome_territory_count_map, locale->territory_code));
571 			count++;
572 			g_hash_table_insert (gnome_territory_count_map, g_strdup (locale->territory_code), GINT_TO_POINTER (count));
573 		}
574         }
575 }
576 
577 static void
collect_locales(void)578 collect_locales (void)
579 {
580         gboolean found_localebin_locales = FALSE;
581         gboolean found_dir_locales = FALSE;
582 
583         if (gnome_available_locales_map == NULL) {
584                 gnome_available_locales_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gnome_locale_free);
585         }
586 
587         found_localebin_locales = collect_locales_from_localebin ();
588 
589         found_dir_locales = collect_locales_from_directory ();
590 
591         if (!(found_localebin_locales || found_dir_locales)) {
592                 g_warning ("Could not read list of available locales from libc, "
593                            "guessing possible locales from available translations, "
594                            "but list may be incomplete!");
595         }
596 
597 	count_languages_and_territories ();
598 }
599 
600 static gint
get_language_count(const char * language)601 get_language_count (const char *language)
602 {
603         if (gnome_language_count_map == NULL) {
604                 collect_locales ();
605         }
606 
607 	return GPOINTER_TO_INT (g_hash_table_lookup (gnome_language_count_map, language));
608 }
609 
610 static gboolean
is_unique_language(const char * language)611 is_unique_language (const char *language)
612 {
613         return get_language_count (language) == 1;
614 }
615 
616 static gint
get_territory_count(const char * territory)617 get_territory_count (const char *territory)
618 {
619         if (gnome_territory_count_map == NULL) {
620                 collect_locales ();
621         }
622 
623 	return GPOINTER_TO_INT (g_hash_table_lookup (gnome_territory_count_map, territory));
624 }
625 
626 static gboolean
is_unique_territory(const char * territory)627 is_unique_territory (const char *territory)
628 {
629         return get_territory_count (territory) == 1;
630 }
631 
632 static gboolean
is_fallback_language(const char * code)633 is_fallback_language (const char *code)
634 {
635         const char *fallback_language_names[] = { "C", "POSIX", NULL };
636         int i;
637 
638         for (i = 0; fallback_language_names[i] != NULL; i++) {
639                 if (strcmp (code, fallback_language_names[i]) == 0) {
640                         return TRUE;
641                 }
642         }
643 
644         return FALSE;
645 }
646 
647 static const char *
get_language(const char * code)648 get_language (const char *code)
649 {
650         const char *name;
651         int         len;
652 
653         g_assert (code != NULL);
654 
655         if (is_fallback_language (code)) {
656                 return "Unspecified";
657         }
658 
659         len = strlen (code);
660         if (len != 2 && len != 3) {
661                 return NULL;
662         }
663 
664         name = (const char *) g_hash_table_lookup (gnome_languages_map, code);
665 
666         return name;
667 }
668 
669 static char *
get_first_item_in_semicolon_list(const char * list)670 get_first_item_in_semicolon_list (const char *list)
671 {
672         char **items;
673         char  *item;
674 
675         /* Some entries in iso codes have multiple values, separated
676          * by semicolons.  Not really sure which one to pick, so
677          * we just arbitrarily pick the first one.
678          */
679         items = g_strsplit (list, "; ", 2);
680 
681         item = g_strdup (items[0]);
682         g_strfreev (items);
683 
684         return item;
685 }
686 
687 static char *
capitalize_utf8_string(const char * str)688 capitalize_utf8_string (const char *str)
689 {
690         char first[8] = { 0 };
691 
692         if (!str)
693                 return NULL;
694 
695         g_unichar_to_utf8 (g_unichar_totitle (g_utf8_get_char (str)), first);
696 
697         return g_strconcat (first, g_utf8_offset_to_pointer (str, 1), NULL);
698 }
699 
700 static char *
get_translated_language(const char * code,const char * locale)701 get_translated_language (const char *code,
702                          const char *locale)
703 {
704         const char *language;
705         char *name;
706 
707         language = get_language (code);
708 
709         name = NULL;
710         if (language != NULL) {
711                 const char *translated_name;
712                 locale_t loc;
713                 locale_t old_locale;
714 
715                 if (locale != NULL) {
716                         loc = newlocale (LC_MESSAGES_MASK, locale, (locale_t) 0);
717                         if (loc == (locale_t) 0)
718                                 return NULL;
719                         old_locale = uselocale (loc);
720                 }
721 
722                 if (is_fallback_language (code)) {
723                         name = g_strdup (_("Unspecified"));
724                 } else {
725                         g_autofree char *tmp = NULL;
726                         translated_name = dgettext ("iso_639", language);
727                         tmp = get_first_item_in_semicolon_list (translated_name);
728                         name = capitalize_utf8_string (tmp);
729                 }
730 
731                 if (locale != NULL) {
732                         uselocale (old_locale);
733                         freelocale (loc);
734                 }
735         }
736 
737         return name;
738 }
739 
740 static const char *
get_territory(const char * code)741 get_territory (const char *code)
742 {
743         const char *name;
744         int         len;
745 
746         g_assert (code != NULL);
747 
748         len = strlen (code);
749         if (len != 2 && len != 3) {
750                 return NULL;
751         }
752 
753         name = (const char *) g_hash_table_lookup (gnome_territories_map, code);
754 
755         return name;
756 }
757 
758 static char *
get_translated_territory(const char * code,const char * locale)759 get_translated_territory (const char *code,
760                           const char *locale)
761 {
762         const char *territory;
763         char       *name;
764 
765         territory = get_territory (code);
766 
767         name = NULL;
768         if (territory != NULL) {
769                 const char *translated_territory;
770                 locale_t loc;
771                 locale_t old_locale = NULL;
772                 g_autofree char *tmp = NULL;
773 
774                 if (locale != NULL) {
775                         loc = newlocale (LC_MESSAGES_MASK, locale, (locale_t) 0);
776                         if (loc == (locale_t) 0)
777                                 return NULL;
778                         old_locale = uselocale (loc);
779                 }
780 
781                 translated_territory = dgettext ("iso_3166", territory);
782                 tmp = get_first_item_in_semicolon_list (translated_territory);
783                 name = capitalize_utf8_string (tmp);
784 
785                 if (locale != NULL) {
786                         uselocale (old_locale);
787                         freelocale (loc);
788                 }
789         }
790 
791         return name;
792 }
793 
794 static void
languages_parse_start_tag(GMarkupParseContext * ctx,const char * element_name,const char ** attr_names,const char ** attr_values,gpointer user_data,GError ** error)795 languages_parse_start_tag (GMarkupParseContext      *ctx,
796                            const char               *element_name,
797                            const char              **attr_names,
798                            const char              **attr_values,
799                            gpointer                  user_data,
800                            GError                  **error)
801 {
802         const char *ccode_longB;
803         const char *ccode_longT;
804         const char *ccode;
805         const char *ccode_id;
806         const char *lang_name;
807 
808         if (! (g_str_equal (element_name, "iso_639_entry") || g_str_equal (element_name, "iso_639_3_entry"))
809             || attr_names == NULL || attr_values == NULL) {
810                 return;
811         }
812 
813         ccode = NULL;
814         ccode_longB = NULL;
815         ccode_longT = NULL;
816         ccode_id = NULL;
817         lang_name = NULL;
818 
819         while (*attr_names && *attr_values) {
820                 if (g_str_equal (*attr_names, "iso_639_1_code")) {
821                         /* skip if empty */
822                         if (**attr_values) {
823                                 if (strlen (*attr_values) != 2) {
824                                         return;
825                                 }
826                                 ccode = *attr_values;
827                         }
828                 } else if (g_str_equal (*attr_names, "iso_639_2B_code")) {
829                         /* skip if empty */
830                         if (**attr_values) {
831                                 if (strlen (*attr_values) != 3) {
832                                         return;
833                                 }
834                                 ccode_longB = *attr_values;
835                         }
836                 } else if (g_str_equal (*attr_names, "iso_639_2T_code")) {
837                         /* skip if empty */
838                         if (**attr_values) {
839                                 if (strlen (*attr_values) != 3) {
840                                         return;
841                                 }
842                                 ccode_longT = *attr_values;
843                         }
844                 } else if (g_str_equal (*attr_names, "id")) {
845                         /* skip if empty */
846                         if (**attr_values) {
847                                 if (strlen (*attr_values) != 2 &&
848                                     strlen (*attr_values) != 3) {
849                                         return;
850                                 }
851                                 ccode_id = *attr_values;
852                         }
853                 } else if (g_str_equal (*attr_names, "name")) {
854                         lang_name = *attr_values;
855                 }
856 
857                 ++attr_names;
858                 ++attr_values;
859         }
860 
861         if (lang_name == NULL) {
862                 return;
863         }
864 
865         if (ccode != NULL) {
866                 g_hash_table_insert (gnome_languages_map,
867                                      g_strdup (ccode),
868                                      g_strdup (lang_name));
869         }
870         if (ccode_longB != NULL) {
871                 g_hash_table_insert (gnome_languages_map,
872                                      g_strdup (ccode_longB),
873                                      g_strdup (lang_name));
874         }
875         if (ccode_longT != NULL) {
876                 g_hash_table_insert (gnome_languages_map,
877                                      g_strdup (ccode_longT),
878                                      g_strdup (lang_name));
879         }
880         if (ccode_id != NULL) {
881                 g_hash_table_insert (gnome_languages_map,
882                                      g_strdup (ccode_id),
883                                      g_strdup (lang_name));
884         }
885 }
886 
887 static void
territories_parse_start_tag(GMarkupParseContext * ctx,const char * element_name,const char ** attr_names,const char ** attr_values,gpointer user_data,GError ** error)888 territories_parse_start_tag (GMarkupParseContext      *ctx,
889                              const char               *element_name,
890                              const char              **attr_names,
891                              const char              **attr_values,
892                              gpointer                  user_data,
893                              GError                  **error)
894 {
895         const char *acode_2;
896         const char *acode_3;
897         const char *ncode;
898         const char *territory_common_name;
899         const char *territory_name;
900 
901         if (! g_str_equal (element_name, "iso_3166_entry") || attr_names == NULL || attr_values == NULL) {
902                 return;
903         }
904 
905         acode_2 = NULL;
906         acode_3 = NULL;
907         ncode = NULL;
908         territory_common_name = NULL;
909         territory_name = NULL;
910 
911         while (*attr_names && *attr_values) {
912                 if (g_str_equal (*attr_names, "alpha_2_code")) {
913                         /* skip if empty */
914                         if (**attr_values) {
915                                 if (strlen (*attr_values) != 2) {
916                                         return;
917                                 }
918                                 acode_2 = *attr_values;
919                         }
920                 } else if (g_str_equal (*attr_names, "alpha_3_code")) {
921                         /* skip if empty */
922                         if (**attr_values) {
923                                 if (strlen (*attr_values) != 3) {
924                                         return;
925                                 }
926                                 acode_3 = *attr_values;
927                         }
928                 } else if (g_str_equal (*attr_names, "numeric_code")) {
929                         /* skip if empty */
930                         if (**attr_values) {
931                                 if (strlen (*attr_values) != 3) {
932                                         return;
933                                 }
934                                 ncode = *attr_values;
935                         }
936                 } else if (g_str_equal (*attr_names, "common_name")) {
937                         /* skip if empty */
938                         if (**attr_values) {
939                                 territory_common_name = *attr_values;
940                         }
941                 } else if (g_str_equal (*attr_names, "name")) {
942                         territory_name = *attr_values;
943                 }
944 
945                 ++attr_names;
946                 ++attr_values;
947         }
948 
949         if (territory_common_name != NULL) {
950                 territory_name = territory_common_name;
951         }
952 
953         if (territory_name == NULL) {
954                 return;
955         }
956 
957         if (acode_2 != NULL) {
958                 g_hash_table_insert (gnome_territories_map,
959                                      g_strdup (acode_2),
960                                      g_strdup (territory_name));
961         }
962         if (acode_3 != NULL) {
963                 g_hash_table_insert (gnome_territories_map,
964                                      g_strdup (acode_3),
965                                      g_strdup (territory_name));
966         }
967         if (ncode != NULL) {
968                 g_hash_table_insert (gnome_territories_map,
969                                      g_strdup (ncode),
970                                      g_strdup (territory_name));
971         }
972 }
973 
974 static void
languages_variant_init(const char * variant)975 languages_variant_init (const char *variant)
976 {
977         gboolean res;
978         gsize    buf_len;
979         g_autofree char *buf = NULL;
980         g_autofree char *filename = NULL;
981         g_autoptr (GError) error = NULL;
982 
983         bindtextdomain (variant, ISO_CODES_LOCALESDIR);
984         bind_textdomain_codeset (variant, "UTF-8");
985 
986         error = NULL;
987         filename = g_strdup_printf (ISO_CODES_DATADIR "/%s.xml", variant);
988         res = g_file_get_contents (filename,
989                                    &buf,
990                                    &buf_len,
991                                    &error);
992         if (res) {
993                 g_autoptr (GMarkupParseContext) ctx = NULL;
994                 GMarkupParser        parser = { languages_parse_start_tag, NULL, NULL, NULL, NULL };
995 
996                 ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
997 
998                 error = NULL;
999                 res = g_markup_parse_context_parse (ctx, buf, buf_len, &error);
1000 
1001                 if (! res) {
1002                         g_warning ("Failed to parse '%s': %s\n",
1003                                    filename,
1004                                    error->message);
1005                 }
1006         } else {
1007                 g_warning ("Failed to load '%s': %s\n",
1008                            filename,
1009                            error->message);
1010         }
1011 }
1012 
1013 static void
languages_init(void)1014 languages_init (void)
1015 {
1016         if (gnome_languages_map)
1017                 return;
1018 
1019         gnome_languages_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1020 
1021         languages_variant_init ("iso_639");
1022         languages_variant_init ("iso_639_3");
1023 }
1024 
1025 static void
territories_init(void)1026 territories_init (void)
1027 {
1028         gboolean res;
1029         gsize    buf_len;
1030         g_autofree char *buf = NULL;
1031         g_autoptr (GError) error = NULL;
1032 
1033         if (gnome_territories_map)
1034                 return;
1035 
1036         bindtextdomain ("iso_3166", ISO_CODES_LOCALESDIR);
1037         bind_textdomain_codeset ("iso_3166", "UTF-8");
1038 
1039         gnome_territories_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1040 
1041         error = NULL;
1042         res = g_file_get_contents (ISO_CODES_DATADIR "/iso_3166.xml",
1043                                    &buf,
1044                                    &buf_len,
1045                                    &error);
1046         if (res) {
1047                 g_autoptr (GMarkupParseContext) ctx = NULL;
1048                 GMarkupParser        parser = { territories_parse_start_tag, NULL, NULL, NULL, NULL };
1049 
1050                 ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
1051 
1052                 error = NULL;
1053                 res = g_markup_parse_context_parse (ctx, buf, buf_len, &error);
1054 
1055                 if (! res) {
1056                         g_warning ("Failed to parse '%s': %s\n",
1057                                    ISO_CODES_DATADIR "/iso_3166.xml",
1058                                    error->message);
1059                 }
1060         } else {
1061                 g_warning ("Failed to load '%s': %s\n",
1062                            ISO_CODES_DATADIR "/iso_3166.xml",
1063                            error->message);
1064         }
1065 }
1066 
1067 /**
1068  * gnome_get_language_from_locale:
1069  * @locale: a locale string
1070  * @translation: (allow-none): a locale string
1071  *
1072  * Gets the language description for @locale. If @translation is
1073  * provided the returned string is translated accordingly.
1074  *
1075  * Return value: (transfer full): the language description. Caller
1076  * takes ownership.
1077  *
1078  * Since: 3.8
1079  */
1080 char *
gnome_get_language_from_locale(const char * locale,const char * translation)1081 gnome_get_language_from_locale (const char *locale,
1082                                 const char *translation)
1083 {
1084         GString *full_language;
1085         g_autofree char *language_code = NULL;
1086         g_autofree char *territory_code = NULL;
1087         g_autofree char *codeset_code = NULL;
1088         g_autofree char *langinfo_codeset = NULL;
1089         g_autofree char *translated_language = NULL;
1090         g_autofree char *translated_territory = NULL;
1091         gboolean is_utf8 = TRUE;
1092 
1093         g_return_val_if_fail (locale != NULL, NULL);
1094         g_return_val_if_fail (*locale != '\0', NULL);
1095 
1096         full_language = g_string_new (NULL);
1097 
1098         languages_init ();
1099         territories_init ();
1100 
1101         gnome_parse_locale (locale,
1102                             &language_code,
1103                             &territory_code,
1104                             &codeset_code,
1105                             NULL);
1106 
1107         if (language_code == NULL) {
1108                 goto out;
1109         }
1110 
1111         translated_language = get_translated_language (language_code, translation);
1112         if (translated_language == NULL) {
1113                 goto out;
1114         }
1115 
1116         full_language = g_string_append (full_language, translated_language);
1117 
1118 	if (is_unique_language (language_code)) {
1119 		goto out;
1120 	}
1121 
1122         if (territory_code != NULL) {
1123                 translated_territory = get_translated_territory (territory_code, translation);
1124         }
1125         if (translated_territory != NULL) {
1126                 g_string_append_printf (full_language,
1127                                         " (%s)",
1128                                         translated_territory);
1129         }
1130 
1131         language_name_get_codeset_details (locale, &langinfo_codeset, &is_utf8);
1132 
1133         if (codeset_code == NULL && langinfo_codeset != NULL) {
1134                 codeset_code = g_strdup (langinfo_codeset);
1135         }
1136 
1137         if (!is_utf8 && codeset_code) {
1138                 g_string_append_printf (full_language,
1139                                         " [%s]",
1140                                         codeset_code);
1141         }
1142 
1143  out:
1144         if (full_language->len == 0) {
1145                 g_string_free (full_language, TRUE);
1146                 return NULL;
1147         }
1148 
1149         return g_string_free (full_language, FALSE);
1150 }
1151 
1152 /**
1153  * gnome_get_country_from_locale:
1154  * @locale: a locale string
1155  * @translation: (allow-none): a locale string
1156  *
1157  * Gets the country description for @locale. If @translation is
1158  * provided the returned string is translated accordingly.
1159  *
1160  * Return value: (transfer full): the country description. Caller
1161  * takes ownership.
1162  *
1163  * Since: 3.8
1164  */
1165 char *
gnome_get_country_from_locale(const char * locale,const char * translation)1166 gnome_get_country_from_locale (const char *locale,
1167                                const char *translation)
1168 {
1169         GString *full_name;
1170         g_autofree char *language_code = NULL;
1171         g_autofree char *territory_code = NULL;
1172         g_autofree char *codeset_code = NULL;
1173         g_autofree char *langinfo_codeset = NULL;
1174         g_autofree char *translated_language = NULL;
1175         g_autofree char *translated_territory = NULL;
1176         gboolean is_utf8 = TRUE;
1177 
1178         g_return_val_if_fail (locale != NULL, NULL);
1179         g_return_val_if_fail (*locale != '\0', NULL);
1180 
1181         full_name = g_string_new (NULL);
1182 
1183         languages_init ();
1184         territories_init ();
1185 
1186         gnome_parse_locale (locale,
1187                             &language_code,
1188                             &territory_code,
1189                             &codeset_code,
1190                             NULL);
1191 
1192         if (territory_code == NULL) {
1193                 goto out;
1194         }
1195 
1196         translated_territory = get_translated_territory (territory_code, translation);
1197         g_string_append (full_name, translated_territory);
1198 
1199 	if (is_unique_territory (territory_code)) {
1200 		goto out;
1201 	}
1202 
1203         if (language_code != NULL) {
1204                 translated_language = get_translated_language (language_code, translation);
1205         }
1206         if (translated_language != NULL) {
1207                 g_string_append_printf (full_name,
1208                                         " (%s)",
1209                                         translated_language);
1210         }
1211 
1212         language_name_get_codeset_details (translation, &langinfo_codeset, &is_utf8);
1213 
1214         if (codeset_code == NULL && langinfo_codeset != NULL) {
1215                 codeset_code = g_strdup (langinfo_codeset);
1216         }
1217 
1218         if (!is_utf8 && codeset_code) {
1219                 g_string_append_printf (full_name,
1220                                         " [%s]",
1221                                         codeset_code);
1222         }
1223 
1224  out:
1225         if (full_name->len == 0) {
1226                 g_string_free (full_name, TRUE);
1227                 return NULL;
1228         }
1229 
1230         return g_string_free (full_name, FALSE);
1231 }
1232 
1233 /**
1234  * gnome_get_all_locales:
1235  *
1236  * Gets all locales.
1237  *
1238  * Return value: (array zero-terminated=1) (element-type utf8) (transfer full):
1239  *   a newly allocated %NULL-terminated string array containing the
1240  *   all locales. Free with g_strfreev().
1241  *
1242  * Since: 3.8
1243  */
1244 char **
gnome_get_all_locales(void)1245 gnome_get_all_locales (void)
1246 {
1247         GHashTableIter iter;
1248         gpointer key, value;
1249         GPtrArray *array;
1250 
1251         if (gnome_available_locales_map == NULL) {
1252                 collect_locales ();
1253         }
1254 
1255         array = g_ptr_array_new ();
1256         g_hash_table_iter_init (&iter, gnome_available_locales_map);
1257         while (g_hash_table_iter_next (&iter, &key, &value)) {
1258                 GnomeLocale *locale;
1259 
1260                 locale = (GnomeLocale *) value;
1261 
1262                 g_ptr_array_add (array, g_strdup (locale->name));
1263         }
1264         g_ptr_array_add (array, NULL);
1265 
1266         return (char **) g_ptr_array_free (array, FALSE);
1267 }
1268 
1269 /**
1270  * gnome_get_language_from_code:
1271  * @code: an ISO 639 code string
1272  * @translation: (allow-none): a locale string
1273  *
1274  * Gets the language name for @code. If @translation is provided the
1275  * returned string is translated accordingly.
1276  *
1277  * Return value: (transfer full): the language name. Caller takes
1278  * ownership.
1279  *
1280  * Since: 3.8
1281  */
1282 char *
gnome_get_language_from_code(const char * code,const char * translation)1283 gnome_get_language_from_code (const char *code,
1284                               const char *translation)
1285 {
1286         g_return_val_if_fail (code != NULL, NULL);
1287 
1288         languages_init ();
1289 
1290         return get_translated_language (code, translation);
1291 }
1292 
1293 /**
1294  * gnome_get_country_from_code:
1295  * @code: an ISO 3166 code string
1296  * @translation: (allow-none): a locale string
1297  *
1298  * Gets the country name for @code. If @translation is provided the
1299  * returned string is translated accordingly.
1300  *
1301  * Return value: (transfer full): the country name. Caller takes
1302  * ownership.
1303  *
1304  * Since: 3.8
1305  */
1306 char *
gnome_get_country_from_code(const char * code,const char * translation)1307 gnome_get_country_from_code (const char *code,
1308                              const char *translation)
1309 {
1310         g_return_val_if_fail (code != NULL, NULL);
1311 
1312         territories_init ();
1313 
1314         return get_translated_territory (code, translation);
1315 }
1316 
1317 #if 0
1318 /**
1319  * gnome_get_input_source_from_locale:
1320  * @locale: a locale string
1321  * @type: (out) (transfer none): location to store the input source
1322  * type
1323  * @id: (out) (transfer none): location to store the input source
1324  * identifier
1325  *
1326  * Gets the default input source's type and identifier for a given
1327  * locale.
1328  *
1329  * Return value: %TRUE if a input source exists or %FALSE otherwise.
1330  *
1331  * Since: 3.8
1332  */
1333 gboolean
1334 gnome_get_input_source_from_locale (const char  *locale,
1335                                     const char **type,
1336                                     const char **id)
1337 {
1338         static GHashTable *table = NULL;
1339         DefaultInputSource *dis;
1340         g_autofree gchar *l_code = NULL;
1341         g_autofree gchar *c_code = NULL;
1342         g_autofree gchar *key = NULL;
1343         gint i;
1344 
1345         g_return_val_if_fail (locale != NULL, FALSE);
1346         g_return_val_if_fail (type != NULL, FALSE);
1347         g_return_val_if_fail (id != NULL, FALSE);
1348 
1349         if (!table) {
1350                 table = g_hash_table_new (g_str_hash, g_str_equal);
1351                 for (i = 0; default_input_sources[i].id; ++i) {
1352                         dis = &default_input_sources[i];
1353                         g_hash_table_insert (table, (gpointer) dis->locale, dis);
1354                 }
1355         }
1356 
1357         if (!gnome_parse_locale (locale, &l_code, &c_code, NULL, NULL))
1358                 return FALSE;
1359 
1360         key = g_strconcat (l_code, "_", c_code, NULL);
1361 
1362         dis = g_hash_table_lookup (table, key);
1363         if (dis) {
1364                 *type = dis->type;
1365                 *id = dis->id;
1366         }
1367         return dis != NULL;
1368 }
1369 #endif
1370