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