1 /*
2 * This file is part of darktable,
3 * Copyright (C) 2018-2020 darktable developers.
4 *
5 * darktable is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * darktable is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "common/l10n.h"
20 #include "common/file_location.h"
21 #include "control/conf.h"
22
23 #include <libintl.h>
24 #include <locale.h>
25 #include <gtk/gtk.h>
26 #include <json-glib/json-glib.h>
27
28 #ifdef __APPLE__
29 #include "osx/osx.h"
30 #endif
31
32 #ifdef _WIN32
33 #include "win/dtwin.h"
34 #include <windows.h>
35 #endif
36
_dt_full_locale_name(const char * locale)37 static gchar* _dt_full_locale_name(const char *locale)
38 {
39 #if defined(__linux__) || defined(__APPLE__)
40 gchar *output = NULL;
41 GError *error = NULL;
42 if(!g_spawn_command_line_sync("locale -a", &output, NULL, NULL, &error))
43 {
44 if(error)
45 {
46 fprintf(stderr, "couldn't check locale: '%s'\n", error->message);
47 g_error_free(error);
48 }
49 }
50 else
51 {
52 if(output)
53 {
54 gchar **locales = g_strsplit (output, "\n", -1);
55 g_free(output);
56 int j = 0;
57 while(locales[j])
58 {
59 if(g_str_has_prefix(locales[j], locale))
60 {
61 // return first found variant - this is most likelly the best one
62 gchar *ret=g_strdup(locales[j]);
63 g_strfreev(locales);
64 return ret;
65 }
66 j++;
67 }
68 }
69 }
70 return NULL;
71 #else
72 // TODO: check a way to do above on windows
73 return NULL;
74 #endif
75 }
76
set_locale(const char * ui_lang,const char * old_env)77 static void set_locale(const char *ui_lang, const char *old_env)
78 {
79 if(ui_lang && *ui_lang)
80 {
81 gchar *full_locale = _dt_full_locale_name(ui_lang);
82 if(full_locale)
83 {
84 g_setenv("LANG", full_locale, TRUE);
85 g_free(full_locale);
86 }
87 g_setenv("LANGUAGE", ui_lang, TRUE);
88 gtk_disable_setlocale();
89 }
90 else if(old_env && *old_env)
91 g_setenv("LANGUAGE", old_env, TRUE);
92 else
93 g_unsetenv("LANGUAGE");
94
95 setlocale(LC_ALL, "");
96 }
97
sort_languages(gconstpointer a,gconstpointer b)98 static gint sort_languages(gconstpointer a, gconstpointer b)
99 {
100 gchar *name_a = g_utf8_casefold(dt_l10n_get_name((const dt_l10n_language_t *)a), -1);
101 gchar *name_b = g_utf8_casefold(dt_l10n_get_name((const dt_l10n_language_t *)b), -1);
102
103 int result = g_strcmp0(name_a, name_b);
104
105 g_free(name_a);
106 g_free(name_b);
107
108 return result;
109 }
110
get_language_names(GList * languages)111 static void get_language_names(GList *languages)
112 {
113 #ifdef HAVE_ISO_CODES
114
115 JsonReader *reader = NULL;
116 JsonParser *parser = NULL;
117 GError *error = NULL;
118 char *filename = NULL;
119 #ifdef __APPLE__
120 char *res_path = dt_osx_get_bundle_res_path();
121 #endif
122
123 #if defined(_WIN32) && !defined(MSYS2_INSTALL)
124 char datadir[PATH_MAX] = { 0 };
125 dt_loc_get_datadir(datadir, sizeof(datadir));
126 filename = g_build_filename(datadir, "..", "iso-codes", "json", "iso_639-2.json", NULL);
127 #else
128 #ifdef __APPLE__
129 if(res_path)
130 filename = g_build_filename(res_path, "share", "iso-codes", "json", "iso_639-2.json", NULL);
131 else
132 #endif
133 filename = g_build_filename(ISO_CODES_LOCATION, "iso_639-2.json", NULL);
134 #endif
135
136 if(!g_file_test(filename, G_FILE_TEST_EXISTS))
137 {
138 fprintf(stderr, "[l10n] error: can't open iso-codes file `%s'\n"
139 " there won't be nicely translated language names in the preferences.\n", filename);
140 goto end;
141 }
142
143 #if defined(_WIN32) && !defined(MSYS2_INSTALL)
144 // on windows we are shipping the translations of iso-codes along ours
145 char localedir[PATH_MAX] = { 0 };
146 dt_loc_get_localedir(localedir, sizeof(localedir));
147 bindtextdomain("iso_639", localedir);
148 #else
149 #ifdef __APPLE__
150 if(res_path)
151 {
152 char localedir[PATH_MAX] = { 0 };
153 dt_loc_get_localedir(localedir, sizeof(localedir));
154 bindtextdomain("iso_639", localedir);
155 }
156 else
157 #endif
158 bindtextdomain("iso_639", ISO_CODES_LOCALEDIR);
159 #endif
160
161 bind_textdomain_codeset("iso_639", "UTF-8");
162
163 parser = json_parser_new();
164 if(!json_parser_load_from_file(parser, filename, &error))
165 {
166 fprintf(stderr, "[l10n] error: parsing json from `%s' failed\n%s\n", filename, error->message);
167 goto end;
168 }
169
170 // go over the json
171 JsonNode *root = json_parser_get_root(parser);
172 if(!root)
173 {
174 fprintf(stderr, "[l10n] error: can't get root node of `%s'\n", filename);
175 goto end;
176 }
177
178 reader = json_reader_new(root);
179
180 if(!json_reader_read_member(reader, "639-2"))
181 {
182 fprintf(stderr, "[l10n] error: unexpected layout of `%s'\n", filename);
183 goto end;
184 }
185
186 if(!json_reader_is_array(reader))
187 {
188 fprintf(stderr, "[l10n] error: unexpected layout of `%s'\n", filename);
189 goto end;
190 }
191
192 char *saved_locale = strdup(setlocale(LC_ALL, NULL));
193
194 int n_elements = json_reader_count_elements(reader);
195 for(int i = 0; i < n_elements; i++)
196 {
197 json_reader_read_element(reader, i);
198 if(!json_reader_is_object(reader))
199 {
200 fprintf(stderr, "[l10n] error: unexpected layout of `%s' (element %d)\n", filename, i);
201 free(saved_locale);
202 saved_locale = NULL;
203 goto end;
204 }
205
206 const char *alpha_2 = NULL, *alpha_3 = NULL, *name = NULL;
207 if(json_reader_read_member(reader, "alpha_2"))
208 alpha_2 = json_reader_get_string_value(reader);
209 json_reader_end_member(reader); // alpha_2
210
211 if(json_reader_read_member(reader, "alpha_3"))
212 alpha_3 = json_reader_get_string_value(reader);
213 json_reader_end_member(reader); // alpha_3
214
215 if(json_reader_read_member(reader, "name"))
216 name = json_reader_get_string_value(reader);
217 json_reader_end_member(reader); // name
218
219 if(name && (alpha_2 || alpha_3))
220 {
221 // check if alpha_2 or alpha_3 is in our translations
222 for(GList *iter = languages; iter; iter = g_list_next(iter))
223 {
224 dt_l10n_language_t *language = (dt_l10n_language_t *)iter->data;
225 if(!g_strcmp0(language->base_code, alpha_2) || !g_strcmp0(language->base_code, alpha_3))
226 {
227 // code taken in parts from GIMP's gimplanguagestore-parser.c
228 g_setenv("LANGUAGE", language->code, TRUE);
229 setlocale (LC_ALL, language->code);
230
231 char *localized_name = g_strdup(dgettext("iso_639", name));
232
233 /* If original and localized names are the same for other than English,
234 * maybe localization failed. Try now in the main dialect. */
235 if(g_strcmp0(name, localized_name) == 0 &&
236 g_strcmp0(language->code, language->base_code) != 0)
237 {
238 g_free(localized_name);
239
240 g_setenv("LANGUAGE", language->base_code, TRUE);
241 setlocale (LC_ALL, language->base_code);
242
243 localized_name = g_strdup(dgettext("iso_639", name));
244 }
245
246 /* there might be several language names; use the first one */
247 char *semicolon = strchr(localized_name, ';');
248
249 if(semicolon)
250 {
251 char *tmp = localized_name;
252 localized_name = g_strndup(localized_name, semicolon - localized_name);
253 g_free(tmp);
254 }
255
256 // we initialize the name to the language code to have something on systems lacking iso-codes, so free it!
257 g_free(language->name);
258 language->name = g_strdup_printf("%s (%s)%s", localized_name, language->code, language->is_default ? " *" : "");
259 g_free(localized_name);
260
261 // we can't break out of the loop here. at least pt is in our list twice!
262 }
263 }
264 }
265 else
266 fprintf(stderr, "[l10n] error: element %d has no name, skipping\n", i);
267
268 json_reader_end_element(reader);
269 }
270
271 if(saved_locale)
272 {
273 setlocale(LC_ALL, saved_locale);
274 free(saved_locale);
275 saved_locale = NULL;
276 }
277
278 json_reader_end_member(reader); // 639-2
279
280 end:
281 // cleanup
282 #ifdef __APPLE__
283 g_free(res_path);
284 #endif
285 g_free(filename);
286 if(error) g_error_free(error);
287 if(reader) g_object_unref(reader);
288 if(parser) g_object_unref(parser);
289
290 #endif // HAVE_ISO_CODES
291 }
292
dt_l10n_init(gboolean init_list)293 dt_l10n_t *dt_l10n_init(gboolean init_list)
294 {
295 dt_l10n_t *result = (dt_l10n_t *)calloc(1, sizeof(dt_l10n_t));
296 result->selected = -1;
297 result->sys_default = -1;
298
299 char *ui_lang = dt_conf_get_string("ui_last/gui_language");
300 const char *old_env = g_getenv("LANGUAGE");
301
302 #if defined(_WIN32)
303 // get the default locale if no language preference was specified in the config file
304 if(!ui_lang || !*ui_lang)
305 {
306 const wchar_t *wcLocaleName = NULL;
307 wcLocaleName = dtwin_get_locale();
308 if(wcLocaleName != NULL)
309 {
310 gchar *langLocale;
311 langLocale = g_utf16_to_utf8(wcLocaleName, -1, NULL, NULL, NULL);
312 if(langLocale != NULL)
313 {
314 g_free(ui_lang);
315 ui_lang = g_strdup(langLocale);
316 }
317 }
318 }
319 #endif // defined (_WIN32)
320
321
322 // prepare the list of available gui translations from which the user can pick in prefs
323 if(init_list)
324 {
325 dt_l10n_language_t *selected = NULL;
326 dt_l10n_language_t *sys_default = NULL;
327
328 dt_l10n_language_t *language = (dt_l10n_language_t *)calloc(1, sizeof(dt_l10n_language_t));
329 language->code = g_strdup("C");
330 language->base_code = g_strdup("C");
331 language->name = g_strdup("English");
332 result->languages = g_list_append(result->languages, language);
333
334 if(g_strcmp0(ui_lang, "C") == 0) selected = language;
335
336 const gchar * const * default_languages = g_get_language_names();
337
338 char localedir[PATH_MAX] = { 0 };
339 dt_loc_get_localedir(localedir, sizeof(localedir));
340 GDir *dir = g_dir_open(localedir, 0, NULL);
341 if(dir)
342 {
343 const gchar *locale;
344 while((locale = g_dir_read_name(dir)))
345 {
346 gchar *testname = g_build_filename(localedir, locale, "LC_MESSAGES", GETTEXT_PACKAGE ".mo", NULL);
347 if(g_file_test(testname, G_FILE_TEST_EXISTS))
348 {
349 language = (dt_l10n_language_t *)calloc(1, sizeof(dt_l10n_language_t));
350 result->languages = g_list_prepend(result->languages, language);
351
352 // some languages have a regional part in the filename, we don't want that for name lookup
353 char *delimiter = strchr(locale, '_');
354 if(delimiter)
355 language->base_code = g_strndup(locale, delimiter - locale);
356 else
357 language->base_code = g_strdup(locale);
358 delimiter = strchr(language->base_code, '@');
359 if(delimiter)
360 {
361 char *tmp = language->base_code;
362 language->base_code = g_strndup(language->base_code, delimiter - language->base_code);
363 g_free(tmp);
364 }
365
366 // check if this is the system default
367 if(sys_default == NULL)
368 {
369 for(const gchar * const * iter = default_languages; *iter; iter++)
370 {
371 if(g_strcmp0(*iter, locale) == 0)
372 {
373 language->is_default = TRUE;
374 sys_default = language;
375 break;
376 }
377 }
378 }
379
380 language->code = g_strdup(locale);
381 language->name = g_strdup_printf("%s%s", locale, language->is_default ? " *" : "");
382
383 if(g_strcmp0(ui_lang, language->code) == 0)
384 selected = language;
385 }
386 g_free(testname);
387 }
388 g_dir_close(dir) ;
389 }
390 else
391 fprintf(stderr, "[l10n] error: can't open directory `%s'\n", localedir);
392
393 // default to English if no other language matched
394 if(!sys_default)
395 {
396 sys_default = g_list_last(result->languages)->data;
397 sys_default->is_default = TRUE;
398 gchar* name = sys_default->name;
399 sys_default->name = g_strdup_printf("%s *", name);
400 g_free(name);
401 }
402
403 // now try to find language names and translations!
404 get_language_names(result->languages);
405
406 // set the requested gui language.
407 // this has to happen before sorting the list as the sort result may depend on the language.
408 set_locale(ui_lang, old_env);
409
410 // sort the list of languages
411 result->languages = g_list_sort(result->languages, sort_languages);
412
413 // find the index of the selected and default languages
414 int i = 0;
415 for(GList *iter = result->languages; iter; iter = g_list_next(iter))
416 {
417 if(iter->data == sys_default) result->sys_default = i;
418 if(iter->data == selected) result->selected = i;
419 i++;
420 }
421
422 if(selected == NULL)
423 result->selected = result->sys_default;
424 }
425 else
426 set_locale(ui_lang, old_env);
427
428 g_free(ui_lang);
429
430 return result;
431 }
432
dt_l10n_get_name(const dt_l10n_language_t * language)433 const char *dt_l10n_get_name(const dt_l10n_language_t *language)
434 {
435 if(!language) return NULL;
436
437 return language->name ? language->name : language->code;
438 }
439
440 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
441 // vim: shiftwidth=2 expandtab tabstop=2 cindent
442 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
443