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