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