1 | #include "config.h" |
2 | |
3 | #include <stdlib.h> |
4 | #include <stdio.h> |
5 | |
6 | #ifdef HAVE_UNISTD_H |
7 | #include <unistd.h> |
8 | #endif |
9 | #include <string.h> |
10 | #include <errno.h> |
11 | #include <locale.h> |
12 | #include <sys/stat.h> |
13 | |
14 | #include <glib.h> |
15 | #include <glib/gi18n.h> |
16 | #include <glib/gstdio.h> |
17 | #include <hb-ot.h> |
18 | |
19 | #include "language-names.h" |
20 | |
21 | #ifdef G_OS_WIN32 |
22 | #define WIN32_LEAN_AND_MEAN |
23 | #include <windows.h> |
24 | #else |
25 | |
26 | #ifndef ISO_CODES_PREFIX |
27 | #define ISO_CODES_PREFIX "/usr" |
28 | #endif |
29 | |
30 | #define ISO_CODES_DATADIR ISO_CODES_PREFIX "/share/xml/iso-codes" |
31 | #define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX "/share/locale" |
32 | #endif |
33 | |
34 | static GHashTable *language_map; |
35 | |
36 | #ifdef G_OS_WIN32 |
37 | /* if we are using native Windows use native Windows API for language names */ |
38 | static BOOL CALLBACK |
39 | get_win32_all_locales_scripts (LPWSTR locale_w, DWORD flags, LPARAM param) |
40 | { |
41 | wchar_t *langname_w = NULL; |
42 | wchar_t locale_abbrev_w[9]; |
43 | gchar *langname, *locale_abbrev, *locale, *p; |
44 | gint i; |
45 | const LCTYPE iso639_lctypes[] = { LOCALE_SISO639LANGNAME, LOCALE_SISO639LANGNAME2 }; |
46 | GHashTable *ht_scripts_langs = (GHashTable *) param; |
47 | PangoLanguage *lang; |
48 | |
49 | gint langname_size, locale_abbrev_size; |
50 | langname_size = GetLocaleInfoEx (locale_w, LOCALE_SLOCALIZEDDISPLAYNAME, langname_w, 0); |
51 | if (langname_size == 0) |
52 | return FALSE; |
53 | |
54 | langname_w = g_new0 (wchar_t, langname_size); |
55 | |
56 | if (langname_size == 0) |
57 | return FALSE; |
58 | |
59 | GetLocaleInfoEx (locale_w, LOCALE_SLOCALIZEDDISPLAYNAME, langname_w, langname_size); |
60 | langname = g_utf16_to_utf8 (langname_w, -1, NULL, NULL, NULL); |
61 | locale = g_utf16_to_utf8 (locale_w, -1, NULL, NULL, NULL); |
62 | p = strchr (locale, '-'); |
63 | lang = pango_language_from_string (locale); |
64 | if (g_hash_table_lookup (ht_scripts_langs, lang) == NULL) |
65 | g_hash_table_insert (ht_scripts_langs, lang, langname); |
66 | |
67 | /* |
68 | * Track 3+-letter ISO639-2/3 language codes as well (these have a max length of 9 including terminating NUL) |
69 | * ISO639-2: iso639_lctypes[0] = LOCALE_SISO639LANGNAME |
70 | * ISO639-3: iso639_lctypes[1] = LOCALE_SISO639LANGNAME2 |
71 | */ |
72 | for (i = 0; i < 2; i++) |
73 | { |
74 | locale_abbrev_size = GetLocaleInfoEx (locale_w, iso639_lctypes[i], locale_abbrev_w, 0); |
75 | if (locale_abbrev_size > 0) |
76 | { |
77 | GetLocaleInfoEx (locale_w, iso639_lctypes[i], locale_abbrev_w, locale_abbrev_size); |
78 | |
79 | locale_abbrev = g_utf16_to_utf8 (locale_abbrev_w, -1, NULL, NULL, NULL); |
80 | lang = pango_language_from_string (locale_abbrev); |
81 | if (g_hash_table_lookup (ht_scripts_langs, lang) == NULL) |
82 | g_hash_table_insert (ht_scripts_langs, lang, langname); |
83 | |
84 | g_free (locale_abbrev); |
85 | } |
86 | } |
87 | |
88 | g_free (locale); |
89 | g_free (langname_w); |
90 | |
91 | return TRUE; |
92 | } |
93 | |
94 | #else /* non-Windows */ |
95 | |
96 | static char * |
97 | get_first_item_in_semicolon_list (const char *list) |
98 | { |
99 | char **items; |
100 | char *item; |
101 | |
102 | items = g_strsplit (string: list, delimiter: "; " , max_tokens: 2); |
103 | |
104 | item = g_strdup (str: items[0]); |
105 | g_strfreev (str_array: items); |
106 | |
107 | return item; |
108 | } |
109 | |
110 | static char * |
111 | capitalize_utf8_string (const char *str) |
112 | { |
113 | char first[8] = { 0 }; |
114 | |
115 | if (!str) |
116 | return NULL; |
117 | |
118 | g_unichar_to_utf8 (c: g_unichar_totitle (c: g_utf8_get_char (p: str)), outbuf: first); |
119 | |
120 | return g_strconcat (string1: first, g_utf8_offset_to_pointer (str, offset: 1), NULL); |
121 | } |
122 | |
123 | static char * |
124 | get_display_name (const char *language) |
125 | { |
126 | const char *translated; |
127 | char *tmp; |
128 | char *name; |
129 | |
130 | translated = dgettext ("iso_639" , language); |
131 | |
132 | tmp = get_first_item_in_semicolon_list (list: translated); |
133 | name = capitalize_utf8_string (str: tmp); |
134 | g_free (mem: tmp); |
135 | |
136 | return name; |
137 | } |
138 | |
139 | static void |
140 | languages_parse_start_tag (GMarkupParseContext *ctx, |
141 | const char *element_name, |
142 | const char **attr_names, |
143 | const char **attr_values, |
144 | gpointer user_data, |
145 | GError **error) |
146 | { |
147 | const char *ccode_longB; |
148 | const char *ccode_longT; |
149 | const char *ccode; |
150 | const char *ccode_id; |
151 | const char *lang_name; |
152 | char *display_name; |
153 | |
154 | if (!(g_str_equal (v1: element_name, v2: "iso_639_entry" ) || |
155 | g_str_equal (v1: element_name, v2: "iso_639_3_entry" )) || |
156 | attr_names == NULL || |
157 | attr_values == NULL) |
158 | return; |
159 | |
160 | ccode = NULL; |
161 | ccode_longB = NULL; |
162 | ccode_longT = NULL; |
163 | ccode_id = NULL; |
164 | lang_name = NULL; |
165 | |
166 | while (*attr_names && *attr_values) |
167 | { |
168 | if (g_str_equal (v1: *attr_names, v2: "iso_639_1_code" )) |
169 | { |
170 | if (**attr_values) |
171 | { |
172 | if (strlen (s: *attr_values) != 2) |
173 | return; |
174 | ccode = *attr_values; |
175 | } |
176 | } |
177 | else if (g_str_equal (v1: *attr_names, v2: "iso_639_2B_code" )) |
178 | { |
179 | if (**attr_values) |
180 | { |
181 | if (strlen (s: *attr_values) != 3) |
182 | return; |
183 | ccode_longB = *attr_values; |
184 | } |
185 | } |
186 | else if (g_str_equal (v1: *attr_names, v2: "iso_639_2T_code" )) |
187 | { |
188 | if (**attr_values) |
189 | { |
190 | if (strlen (s: *attr_values) != 3) |
191 | return; |
192 | ccode_longT = *attr_values; |
193 | } |
194 | } |
195 | else if (g_str_equal (v1: *attr_names, v2: "id" )) |
196 | { |
197 | if (**attr_values) |
198 | { |
199 | if (strlen (s: *attr_values) != 2 && |
200 | strlen (s: *attr_values) != 3) |
201 | return; |
202 | ccode_id = *attr_values; |
203 | } |
204 | } |
205 | else if (g_str_equal (v1: *attr_names, v2: "name" )) |
206 | { |
207 | lang_name = *attr_values; |
208 | } |
209 | |
210 | ++attr_names; |
211 | ++attr_values; |
212 | } |
213 | |
214 | if (lang_name == NULL) |
215 | return; |
216 | |
217 | display_name = get_display_name (language: lang_name); |
218 | |
219 | if (ccode != NULL) |
220 | g_hash_table_insert (hash_table: language_map, |
221 | key: pango_language_from_string (language: ccode), |
222 | value: g_strdup (str: display_name)); |
223 | |
224 | if (ccode_longB != NULL) |
225 | g_hash_table_insert (hash_table: language_map, |
226 | key: pango_language_from_string (language: ccode_longB), |
227 | value: g_strdup (str: display_name)); |
228 | |
229 | if (ccode_longT != NULL) |
230 | g_hash_table_insert (hash_table: language_map, |
231 | key: pango_language_from_string (language: ccode_longT), |
232 | value: g_strdup (str: display_name)); |
233 | |
234 | if (ccode_id != NULL) |
235 | g_hash_table_insert (hash_table: language_map, |
236 | key: pango_language_from_string (language: ccode_id), |
237 | value: g_strdup (str: display_name)); |
238 | |
239 | g_free (mem: display_name); |
240 | } |
241 | |
242 | static void |
243 | languages_variant_init (const char *variant) |
244 | { |
245 | gboolean res; |
246 | gsize buf_len; |
247 | char *buf = NULL; |
248 | char *filename = NULL; |
249 | GError *error = NULL; |
250 | |
251 | bindtextdomain (domainname: variant, ISO_CODES_LOCALESDIR); |
252 | bind_textdomain_codeset (domainname: variant, codeset: "UTF-8" ); |
253 | |
254 | error = NULL; |
255 | filename = g_strconcat (ISO_CODES_DATADIR, "/" , variant, ".xml" , NULL); |
256 | res = g_file_get_contents (filename, contents: &buf, length: &buf_len, error: &error); |
257 | if (res) |
258 | { |
259 | GMarkupParseContext *ctx = NULL; |
260 | GMarkupParser parser = { languages_parse_start_tag, NULL, NULL, NULL, NULL }; |
261 | |
262 | ctx = g_markup_parse_context_new (parser: &parser, flags: 0, NULL, NULL); |
263 | |
264 | g_free (mem: error); |
265 | error = NULL; |
266 | res = g_markup_parse_context_parse (context: ctx, text: buf, text_len: buf_len, error: &error); |
267 | g_free (mem: ctx); |
268 | |
269 | if (!res) |
270 | g_warning ("Failed to parse '%s': %s\n" , filename, error->message); |
271 | } |
272 | else |
273 | g_warning ("Failed to load '%s': %s\n" , filename, error->message); |
274 | |
275 | g_clear_error (err: &error); |
276 | g_free (mem: filename); |
277 | g_free (mem: buf); |
278 | } |
279 | #endif |
280 | |
281 | static void |
282 | languages_init (void) |
283 | { |
284 | if (language_map) |
285 | return; |
286 | |
287 | language_map = g_hash_table_new_full (NULL, NULL, NULL, value_destroy_func: g_free); |
288 | |
289 | #ifdef G_OS_WIN32 |
290 | g_return_if_fail (EnumSystemLocalesEx (&get_win32_all_locales_scripts, LOCALE_ALL, (LPARAM) language_map, NULL)); |
291 | #else |
292 | languages_variant_init (variant: "iso_639" ); |
293 | languages_variant_init (variant: "iso_639_3" ); |
294 | #endif |
295 | } |
296 | |
297 | const char * |
298 | get_language_name (PangoLanguage *language) |
299 | { |
300 | languages_init (); |
301 | |
302 | return (const char *) g_hash_table_lookup (hash_table: language_map, key: language); |
303 | } |
304 | |
305 | const char * |
306 | get_language_name_for_tag (guint32 tag) |
307 | { |
308 | hb_language_t lang; |
309 | const char *s; |
310 | |
311 | lang = hb_ot_tag_to_language (tag); |
312 | s = hb_language_to_string (language: lang); |
313 | |
314 | return get_language_name (language: pango_language_from_string (language: s)); |
315 | } |
316 | |