1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
3
* Copyright 2008 Red Hat, Inc,
4
* 2007 William Jon McCann <mccann@jhu.edu>
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.
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.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
* Written by : William Jon McCann <mccann@jhu.edu>
21
* Ray Strode <rstrode@redhat.com>
37
#include <glib/gi18n.h>
38
#include <glib/gstdio.h>
40
#include "gdm-languages.h"
46
#include "locarchive.h"
48
#define ALIASES_FILE DATADIR "/gdm/locale.alias"
49
#define ARCHIVE_FILE LIBLOCALEDIR "/locale-archive"
50
#define ISO_CODES_DATADIR ISO_CODES_PREFIX "/share/xml/iso-codes"
51
#define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX "/share/locale"
53
typedef struct _GdmLocale {
62
static GHashTable *gdm_languages_map;
63
static GHashTable *gdm_territories_map;
64
static GHashTable *gdm_available_locales_map;
66
static char * construct_language_name (const char *language,
67
const char *territory,
69
const char *modifier);
71
static gboolean language_name_is_valid (const char *language_name);
74
gdm_locale_free (GdmLocale *locale)
81
g_free (locale->name);
82
g_free (locale->codeset);
83
g_free (locale->modifier);
88
normalize_codeset (const char *codeset)
90
char *normalized_codeset;
94
normalized_codeset = g_strdup (codeset);
96
if (codeset != NULL) {
97
for (p = codeset, q = normalized_codeset;
100
if (*p == '-' || *p == '_') {
104
*q = g_ascii_tolower (*p);
110
return normalized_codeset;
114
* According to http://en.wikipedia.org/wiki/Locale
115
* locale names are of the form:
116
* [language[_territory][.codeset][@modifier]]
119
gdm_parse_language_name (const char *name,
120
char **language_codep,
121
char **territory_codep,
126
GMatchInfo *match_info;
129
gchar *normalized_codeset = NULL;
130
gchar *normalized_name = NULL;
133
re = g_regex_new ("^(?P<language>[^_.@[:space:]]+)"
134
"(_(?P<territory>[[:upper:]]+))?"
135
"(\\.(?P<codeset>[-_0-9a-zA-Z]+))?"
136
"(@(?P<modifier>[[:ascii:]]+))?$",
139
g_critical ("%s", error->message);
143
if (!g_regex_match (re, name, 0, &match_info) ||
144
g_match_info_is_partial_match (match_info)) {
145
g_match_info_free (match_info);
147
g_warning ("locale %s isn't valid\n", name);
151
res = g_match_info_matches (match_info);
153
g_warning ("Unable to parse locale: %s", name);
157
if (language_codep != NULL) {
158
*language_codep = g_match_info_fetch_named (match_info, "language");
161
if (territory_codep != NULL) {
162
*territory_codep = g_match_info_fetch_named (match_info, "territory");
164
if (*territory_codep != NULL &&
165
*territory_codep[0] == '\0') {
166
g_free (*territory_codep);
167
*territory_codep = NULL;
171
if (codesetp != NULL) {
172
*codesetp = g_match_info_fetch_named (match_info, "codeset");
174
if (*codesetp != NULL &&
175
*codesetp[0] == '\0') {
181
if (modifierp != NULL) {
182
*modifierp = g_match_info_fetch_named (match_info, "modifier");
184
if (*modifierp != NULL &&
185
*modifierp[0] == '\0') {
191
if (codesetp != NULL && *codesetp != NULL) {
192
normalized_codeset = normalize_codeset (*codesetp);
193
normalized_name = construct_language_name (language_codep ? *language_codep : NULL,
194
territory_codep ? *territory_codep : NULL,
196
modifierp ? *modifierp : NULL);
198
if (language_name_is_valid (normalized_name)) {
200
*codesetp = normalized_codeset;
202
g_free (normalized_codeset);
204
g_free (normalized_name);
207
g_match_info_free (match_info);
212
construct_language_name (const char *language,
213
const char *territory,
215
const char *modifier)
219
g_assert (language[0] != 0);
220
g_assert (territory == NULL || territory[0] != 0);
221
g_assert (codeset == NULL || codeset[0] != 0);
222
g_assert (modifier == NULL || modifier[0] != 0);
224
name = g_strdup_printf ("%s%s%s%s%s%s%s",
226
territory != NULL? "_" : "",
227
territory != NULL? territory : "",
228
codeset != NULL? "." : "",
229
codeset != NULL? codeset : "",
230
modifier != NULL? "@" : "",
231
modifier != NULL? modifier : "");
237
gdm_normalize_language_name (const char *name)
239
char *normalized_name;
241
char *territory_code;
245
if (name[0] == '\0') {
249
gdm_parse_language_name (name,
252
&codeset, &modifier);
254
normalized_name = construct_language_name (language_code,
257
g_free (language_code);
258
g_free (territory_code);
262
return normalized_name;
266
language_name_is_valid (const char *language_name)
270
#ifdef WITH_INCOMPLETE_LOCALES
271
int lc_type_id = LC_CTYPE;
273
int lc_type_id = LC_MESSAGES;
276
old_locale = g_strdup (setlocale (lc_type_id, NULL));
277
is_valid = setlocale (lc_type_id, language_name) != NULL;
278
setlocale (lc_type_id, old_locale);
285
language_name_get_codeset_details (const char *language_name,
292
old_locale = g_strdup (setlocale (LC_CTYPE, NULL));
294
if (setlocale (LC_CTYPE, language_name) == NULL) {
299
codeset = nl_langinfo (CODESET);
301
if (pcodeset != NULL) {
305
if (is_utf8 != NULL) {
306
codeset = normalize_codeset (codeset);
308
*is_utf8 = strcmp (codeset, "utf8") == 0;
312
setlocale (LC_CTYPE, old_locale);
317
language_name_has_translations (const char *language_name)
322
gboolean has_translations;
324
path = g_build_filename (GNOMELOCALEDIR, language_name, "LC_MESSAGES", NULL);
326
has_translations = FALSE;
327
dir = g_dir_open (path, 0, NULL);
335
name = g_dir_read_name (dir);
341
if (g_str_has_suffix (name, ".mo")) {
342
has_translations = TRUE;
345
} while (name != NULL);
349
return has_translations;
353
add_locale (const char *language_name,
357
GdmLocale *old_locale;
361
g_return_val_if_fail (language_name != NULL, FALSE);
363
language_name_get_codeset_details (language_name, NULL, &is_utf8);
366
name = g_strdup (language_name);
367
} else if (utf8_only) {
368
name = g_strdup_printf ("%s.utf8", language_name);
370
language_name_get_codeset_details (name, NULL, &is_utf8);
376
name = g_strdup (language_name);
379
if (!language_name_is_valid (name)) {
380
g_warning ("Your locale '%s' was failed by setlocale()", name);
385
locale = g_new0 (GdmLocale, 1);
386
gdm_parse_language_name (name,
387
&locale->language_code,
388
&locale->territory_code,
394
#ifdef WITH_INCOMPLETE_LOCALES
396
if (locale->territory_code == NULL || locale->modifier) {
397
gdm_locale_free (locale);
403
locale->id = construct_language_name (locale->language_code, locale->territory_code,
404
NULL, locale->modifier);
405
locale->name = construct_language_name (locale->language_code, locale->territory_code,
406
locale->codeset, locale->modifier);
408
#ifndef WITH_INCOMPLETE_LOCALES
409
if (!language_name_has_translations (locale->name) &&
410
!language_name_has_translations (locale->id) &&
411
!language_name_has_translations (locale->language_code) &&
413
g_warning ("Your locale '%s' doesn't have message catalog files",
415
gdm_locale_free (locale);
422
locale->id = g_strdup (locale->name);
425
old_locale = g_hash_table_lookup (gdm_available_locales_map, locale->id);
426
if (old_locale != NULL) {
427
if (strlen (old_locale->name) > strlen (locale->name)) {
428
gdm_locale_free (locale);
433
g_hash_table_insert (gdm_available_locales_map, g_strdup (locale->id), locale);
441
uint32_t locrec_offset;
445
collect_locales_from_archive (void)
450
struct locarhead *head;
451
struct namehashent *namehashtab;
452
struct nameent *names;
456
gboolean locales_collected;
459
mapped = g_mapped_file_new (ARCHIVE_FILE, FALSE, &error);
460
if (mapped == NULL) {
461
g_warning ("Mapping failed for %s: %s", ARCHIVE_FILE, error->message);
462
g_error_free (error);
466
locales_collected = FALSE;
468
addr = g_mapped_file_get_contents (mapped);
469
len = g_mapped_file_get_length (mapped);
471
head = (struct locarhead *) addr;
472
if (head->namehash_offset + head->namehash_size > len
473
|| head->string_offset + head->string_size > len
474
|| head->locrectab_offset + head->locrectab_size > len
475
|| head->sumhash_offset + head->sumhash_size > len) {
479
namehashtab = (struct namehashent *) (addr + head->namehash_offset);
481
names = (struct nameent *) g_new0 (struct nameent, head->namehash_used);
482
for (cnt = used = 0; cnt < head->namehash_size; ++cnt) {
483
if (namehashtab[cnt].locrec_offset != 0) {
484
names[used].name = addr + namehashtab[cnt].name_offset;
485
names[used++].locrec_offset = namehashtab[cnt].locrec_offset;
489
for (cnt = 0; cnt < used; ++cnt) {
490
add_locale (names[cnt].name, TRUE);
495
locales_collected = TRUE;
498
g_mapped_file_unref (mapped);
499
return locales_collected;
503
select_dirs (const struct dirent *dirent)
507
if (strcmp (dirent->d_name, ".") != 0 && strcmp (dirent->d_name, "..") != 0) {
510
#ifdef _DIRENT_HAVE_D_TYPE
511
if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK) {
512
mode = DTTOIF (dirent->d_type);
519
path = g_build_filename (LIBLOCALEDIR, dirent->d_name, NULL);
520
if (g_stat (path, &st) == 0) {
526
result = S_ISDIR (mode);
533
collect_locales_from_directory (void)
535
struct dirent **dirents;
539
ndirents = scandir (LIBLOCALEDIR, &dirents, select_dirs, alphasort);
541
for (cnt = 0; cnt < ndirents; ++cnt) {
542
add_locale (dirents[cnt]->d_name, TRUE);
551
collect_locales_from_locale_file (const char *locale_file)
557
if (locale_file == NULL)
560
langlist = fopen (locale_file, "r");
562
if (langlist == NULL)
571
getsret = fgets (curline, sizeof (curline), langlist);
575
if (curline[0] <= ' ' ||
579
name = strtok (curline, " \t\r\n");
583
lang = strtok (NULL, " \t\r\n");
587
lang_list = g_strsplit (lang, ",", -1);
588
if (lang_list == NULL)
592
for (i = 0; lang_list[i] != NULL; i++) {
593
if (add_locale (lang_list[i], FALSE)) {
597
g_strfreev (lang_list);
604
collect_locales (void)
607
if (gdm_available_locales_map == NULL) {
608
gdm_available_locales_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gdm_locale_free);
611
if (!collect_locales_from_archive ()) {
612
#ifndef WITH_INCOMPLETE_LOCALES
613
g_warning ("Could not read list of available locales from libc, "
614
"guessing possible locales from available translations, "
615
"but list may be incomplete!");
618
collect_locales_from_directory ();
620
collect_locales_from_locale_file (ALIASES_FILE);
624
is_fallback_language (const char *code)
626
const char *fallback_language_names[] = { "C", "POSIX", NULL };
629
for (i = 0; fallback_language_names[i] != NULL; i++) {
630
if (strcmp (code, fallback_language_names[i]) == 0) {
639
get_language (const char *code)
644
g_assert (code != NULL);
646
if (is_fallback_language (code)) {
647
return "Unspecified";
651
if (len != 2 && len != 3) {
655
name = (const char *) g_hash_table_lookup (gdm_languages_map, code);
661
get_first_item_in_semicolon_list (const char *list)
666
/* Some entries in iso codes have multiple values, separated
667
* by semicolons. Not really sure which one to pick, so
668
* we just arbitrarily pick the first one.
670
items = g_strsplit (list, "; ", 2);
672
item = g_strdup (items[0]);
679
get_translated_language (const char *code,
682
const char *language;
685
language = get_language (code);
688
if (language != NULL) {
689
const char *translated_name;
692
if (locale != NULL) {
693
old_locale = g_strdup (setlocale (LC_MESSAGES, NULL));
694
setlocale (LC_MESSAGES, locale);
697
if (is_fallback_language (code)) {
698
name = _("Unspecified");
700
translated_name = dgettext ("iso_639", language);
701
name = get_first_item_in_semicolon_list (translated_name);
704
if (locale != NULL) {
705
setlocale (LC_MESSAGES, old_locale);
714
get_territory (const char *code)
719
g_assert (code != NULL);
722
if (len != 2 && len != 3) {
726
name = (const char *) g_hash_table_lookup (gdm_territories_map, code);
732
get_translated_territory (const char *code,
735
const char *territory;
738
territory = get_territory (code);
741
if (territory != NULL) {
742
const char *translated_territory;
745
if (locale != NULL) {
746
old_locale = g_strdup (setlocale (LC_MESSAGES, NULL));
747
setlocale (LC_MESSAGES, locale);
750
translated_territory = dgettext ("iso_3166", territory);
751
name = get_first_item_in_semicolon_list (translated_territory);
753
if (locale != NULL) {
754
setlocale (LC_MESSAGES, old_locale);
763
languages_parse_start_tag (GMarkupParseContext *ctx,
764
const char *element_name,
765
const char **attr_names,
766
const char **attr_values,
770
const char *ccode_longB;
771
const char *ccode_longT;
773
const char *lang_name;
775
if (! g_str_equal (element_name, "iso_639_entry") || attr_names == NULL || attr_values == NULL) {
784
while (*attr_names && *attr_values) {
785
if (g_str_equal (*attr_names, "iso_639_1_code")) {
788
if (strlen (*attr_values) != 2) {
791
ccode = *attr_values;
793
} else if (g_str_equal (*attr_names, "iso_639_2B_code")) {
796
if (strlen (*attr_values) != 3) {
799
ccode_longB = *attr_values;
801
} else if (g_str_equal (*attr_names, "iso_639_2T_code")) {
804
if (strlen (*attr_values) != 3) {
807
ccode_longT = *attr_values;
809
} else if (g_str_equal (*attr_names, "name")) {
810
lang_name = *attr_values;
817
if (lang_name == NULL) {
822
g_hash_table_insert (gdm_languages_map,
824
g_strdup (lang_name));
826
if (ccode_longB != NULL) {
827
g_hash_table_insert (gdm_languages_map,
828
g_strdup (ccode_longB),
829
g_strdup (lang_name));
831
if (ccode_longT != NULL) {
832
g_hash_table_insert (gdm_languages_map,
833
g_strdup (ccode_longT),
834
g_strdup (lang_name));
839
territories_parse_start_tag (GMarkupParseContext *ctx,
840
const char *element_name,
841
const char **attr_names,
842
const char **attr_values,
849
const char *territory_common_name;
850
const char *territory_name;
852
if (! g_str_equal (element_name, "iso_3166_entry") || attr_names == NULL || attr_values == NULL) {
859
territory_common_name = NULL;
860
territory_name = NULL;
862
while (*attr_names && *attr_values) {
863
if (g_str_equal (*attr_names, "alpha_2_code")) {
866
if (strlen (*attr_values) != 2) {
869
acode_2 = *attr_values;
871
} else if (g_str_equal (*attr_names, "alpha_3_code")) {
874
if (strlen (*attr_values) != 3) {
877
acode_3 = *attr_values;
879
} else if (g_str_equal (*attr_names, "numeric_code")) {
882
if (strlen (*attr_values) != 3) {
885
ncode = *attr_values;
887
} else if (g_str_equal (*attr_names, "common_name")) {
890
territory_common_name = *attr_values;
892
} else if (g_str_equal (*attr_names, "name")) {
893
territory_name = *attr_values;
900
if (territory_common_name != NULL) {
901
territory_name = territory_common_name;
904
if (territory_name == NULL) {
908
if (acode_2 != NULL) {
909
g_hash_table_insert (gdm_territories_map,
911
g_strdup (territory_name));
913
if (acode_3 != NULL) {
914
g_hash_table_insert (gdm_territories_map,
916
g_strdup (territory_name));
919
g_hash_table_insert (gdm_territories_map,
921
g_strdup (territory_name));
926
languages_init (void)
933
bindtextdomain ("iso_639", ISO_CODES_LOCALESDIR);
934
bind_textdomain_codeset ("iso_639", "UTF-8");
936
gdm_languages_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
939
res = g_file_get_contents (ISO_CODES_DATADIR "/iso_639.xml",
944
GMarkupParseContext *ctx;
945
GMarkupParser parser = { languages_parse_start_tag, NULL, NULL, NULL, NULL };
947
ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
950
res = g_markup_parse_context_parse (ctx, buf, buf_len, &error);
953
g_warning ("Failed to parse '%s': %s\n",
954
ISO_CODES_DATADIR "/iso_639.xml",
956
g_error_free (error);
959
g_markup_parse_context_free (ctx);
962
g_warning ("Failed to load '%s': %s\n",
963
ISO_CODES_DATADIR "/iso_639.xml",
965
g_error_free (error);
970
territories_init (void)
977
bindtextdomain ("iso_3166", ISO_CODES_LOCALESDIR);
978
bind_textdomain_codeset ("iso_3166", "UTF-8");
980
gdm_territories_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
983
res = g_file_get_contents (ISO_CODES_DATADIR "/iso_3166.xml",
988
GMarkupParseContext *ctx;
989
GMarkupParser parser = { territories_parse_start_tag, NULL, NULL, NULL, NULL };
991
ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
994
res = g_markup_parse_context_parse (ctx, buf, buf_len, &error);
997
g_warning ("Failed to parse '%s': %s\n",
998
ISO_CODES_DATADIR "/iso_3166.xml",
1000
g_error_free (error);
1003
g_markup_parse_context_free (ctx);
1006
g_warning ("Failed to load '%s': %s\n",
1007
ISO_CODES_DATADIR "/iso_3166.xml",
1009
g_error_free (error);
1014
gdm_get_language_from_name (const char *name,
1017
char *full_language;
1018
char *language_code;
1019
char *territory_code;
1021
char *langinfo_codeset;
1023
const char *territory;
1024
gboolean is_utf8 = TRUE;
1026
if (gdm_languages_map == NULL) {
1030
if (gdm_territories_map == NULL) {
1031
territories_init ();
1034
language_code = NULL;
1035
territory_code = NULL;
1036
codeset_code = NULL;
1038
gdm_parse_language_name (name, &language_code, &territory_code,
1039
&codeset_code, NULL);
1041
if (language_code == NULL) {
1045
language = get_translated_language (language_code, locale);
1047
if (territory_code != NULL) {
1048
territory = get_translated_territory (territory_code, locale);
1053
language_name_get_codeset_details (name, &langinfo_codeset, &is_utf8);
1055
if (codeset_code == NULL && langinfo_codeset != NULL) {
1056
codeset_code = g_strdup (langinfo_codeset);
1059
full_language = NULL;
1062
full_language = g_strdup (language);
1068
language = full_language;
1069
full_language = g_strdup_printf ("%s (%s)",
1070
language, territory);
1074
if (!is_utf8 && codeset_code) {
1075
language = full_language;
1076
full_language = g_strdup_printf ("%s [%s]",
1077
language, codeset_code);
1082
g_free (language_code);
1083
g_free (territory_code);
1084
g_free (codeset_code);
1086
return full_language;
1090
gdm_get_all_language_names (void)
1092
GHashTableIter iter;
1093
gpointer key, value;
1096
if (gdm_available_locales_map == NULL) {
1100
array = g_ptr_array_new ();
1101
g_hash_table_iter_init (&iter, gdm_available_locales_map);
1102
while (g_hash_table_iter_next (&iter, &key, &value)) {
1105
locale = (GdmLocale *) value;
1107
g_ptr_array_add (array, g_strdup (locale->name));
1109
g_ptr_array_add (array, NULL);
1111
return (char **) g_ptr_array_free (array, FALSE);