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