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