From 34e4c4af77fea65c7819bfeb0bbeb74485560a7c Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Fri, 7 Jan 2022 19:14:46 -0600 Subject: [PATCH 1/4] gsettingsschema: fix l10n=time attribute It's supposed to be possible to translate settings values using LC_TIME rather than LC_MESSAGES to determine which translation to use, but Sebastian Keller noticed that it's not working properly. I've implemented his proposed solution, which is to actually temporarily change LC_MESSAGES to match LC_TIME for just as long as necessary to force gettext to use the desired message catalog. Fixes #2575 --- gio/gsettingsschema.c | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/gio/gsettingsschema.c b/gio/gsettingsschema.c index fb3bb7012..b3ac2f189 100644 --- a/gio/gsettingsschema.c +++ b/gio/gsettingsschema.c @@ -32,6 +32,11 @@ #include #include +#ifdef HAVE_XLOCALE_H +/* Needed on macOS and FreeBSD for uselocale() */ +#include +#endif + /** * SECTION:gsettingsschema * @short_description: Introspecting and controlling the loading @@ -1416,9 +1421,14 @@ g_settings_schema_key_range_fixup (GSettingsSchemaKey *key, GVariant * g_settings_schema_key_get_translated_default (GSettingsSchemaKey *key) { - const gchar *translated; + const gchar *translated = NULL; GError *error = NULL; const gchar *domain; +#ifdef HAVE_USELOCALE + const gchar *lc_time; + locale_t old_locale; + locale_t locale; +#endif GVariant *value; domain = g_settings_schema_get_gettext_domain (key->schema); @@ -1427,9 +1437,25 @@ g_settings_schema_key_get_translated_default (GSettingsSchemaKey *key) /* translation not requested for this key */ return NULL; +#ifdef HAVE_USELOCALE if (key->lc_char == 't') - translated = g_dcgettext (domain, key->unparsed, LC_TIME); - else + { + lc_time = setlocale (LC_TIME, NULL); + if (lc_time) + { + locale = newlocale (LC_MESSAGES_MASK, lc_time, (locale_t) 0); + if (locale != (locale_t) 0) + { + old_locale = uselocale (locale); + translated = g_dgettext (domain, key->unparsed); + uselocale (old_locale); + freelocale (locale); + } + } + } +#endif + + if (translated == NULL) translated = g_dgettext (domain, key->unparsed); if (translated == key->unparsed) From 92de7298c80cabdf13990be63ba3ce68794ff327 Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Fri, 21 Jul 2023 13:48:34 -0500 Subject: [PATCH 2/4] Add test for l10n="time" gschema attribute This is a copy of the existing test_l10n, modified to use LC_TIME instead of LC_MESSAGES. It's not safe as each call to g_setenv() or setlocale() could cause the test to crash; there is no safe way to change a threaded process's environment, and a threaded process's locale can only be safely changed using uselocale(), not with setlocale(). The calls to g_setenv() are definitely not needed on Linux. I wonder whether removing these will break the test on other platforms? The calls to setlocale() should be replaced by a dance of uselocale() -> duplocale() -> newlocale() -> uselocale() on Linux. But this is not portable and this is a cross-platform test. We would have to make the test platform-specific to do this. macOS and at least FreeBSD provide these functions via xlocale.h, but this isn't portable. --- gio/tests/de.po | 3 ++ gio/tests/gsettings.c | 51 +++++++++++++++++++++++++ gio/tests/org.gtk.test.gschema.xml.orig | 3 ++ 3 files changed, 57 insertions(+) diff --git a/gio/tests/de.po b/gio/tests/de.po index eed161c68..00531960b 100644 --- a/gio/tests/de.po +++ b/gio/tests/de.po @@ -15,3 +15,6 @@ msgstr "\"Unbenannt\"" msgctxt "keyboard label" msgid "\"BackSpace\"" msgstr "\"Löschen\"" + +msgid "\"12:00 AM\"" +msgstr "\"00:00\"" \ No newline at end of file diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c index 182e79e0a..0132ab409 100644 --- a/gio/tests/gsettings.c +++ b/gio/tests/gsettings.c @@ -853,6 +853,56 @@ test_l10n_context (void) g_object_unref (settings); } +/* Test use of l10n="time" and LC_TIME. */ +static void +test_l10n_time (void) +{ + GSettings *settings; + gchar *str; + gchar *locale; + + g_test_summary ("Test that l10n='time' attribute uses the correct category for translations"); + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2575"); + + bindtextdomain ("test", locale_dir); + bind_textdomain_codeset ("test", "UTF-8"); + + locale = g_strdup (setlocale (LC_TIME, NULL)); + + settings = g_settings_new ("org.gtk.test.localized"); + + g_setenv ("LC_TIME", "C", TRUE); + setlocale (LC_TIME, "C"); + str = g_settings_get_string (settings, "midnight"); + g_setenv ("LC_TIME", locale, TRUE); + setlocale (LC_TIME, locale); + + g_assert_cmpstr (str, ==, "12:00 AM"); + g_free (str); + str = NULL; + + g_setenv ("LC_TIME", "de_DE.UTF-8", TRUE); + setlocale (LC_TIME, "de_DE.UTF-8"); + /* Only do the test if translation is actually working... */ + if (g_str_equal (dgettext ("test", "\"12:00 AM\""), "\"00:00\"")) + { + str = g_settings_get_string (settings, "midnight"); + + g_assert_cmpstr (str, ==, "00:00"); + g_free (str); + str = NULL; + } + else + { + g_test_skip ("translation is not working"); + } + + g_setenv ("LC_TIME", locale, TRUE); + setlocale (LC_TIME, locale); + g_free (locale); + g_object_unref (settings); +} + enum { PROP_0, @@ -3109,6 +3159,7 @@ main (int argc, char *argv[]) g_test_add_func ("/gsettings/l10n", test_l10n); g_test_add_func ("/gsettings/l10n-context", test_l10n_context); + g_test_add_func ("/gsettings/l10n-time", test_l10n_time); g_test_add_func ("/gsettings/delay-apply", test_delay_apply); g_test_add_func ("/gsettings/delay-revert", test_delay_revert); diff --git a/gio/tests/org.gtk.test.gschema.xml.orig b/gio/tests/org.gtk.test.gschema.xml.orig index aad4e54df..e01183897 100644 --- a/gio/tests/org.gtk.test.gschema.xml.orig +++ b/gio/tests/org.gtk.test.gschema.xml.orig @@ -83,6 +83,9 @@ "BackSpace" + + "12:00 AM" + From 736000e49eec929781afea52bcef58e86c4dde11 Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Mon, 24 Jul 2023 13:27:47 -0500 Subject: [PATCH 3/4] Remove setenv() use from GSettings l10n tests This definitely does not do anything on Linux. I bet it's not needed on other platforms, either. It's unsafe and may crash; there is no safe way to mutate the environment in threaded programs. --- gio/tests/gsettings.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c index 0132ab409..d783ea6bd 100644 --- a/gio/tests/gsettings.c +++ b/gio/tests/gsettings.c @@ -776,17 +776,14 @@ test_l10n (void) settings = g_settings_new ("org.gtk.test.localized"); - g_setenv ("LC_MESSAGES", "C", TRUE); setlocale (LC_MESSAGES, "C"); str = g_settings_get_string (settings, "error-message"); - g_setenv ("LC_MESSAGES", locale, TRUE); setlocale (LC_MESSAGES, locale); g_assert_cmpstr (str, ==, "Unnamed"); g_free (str); str = NULL; - g_setenv ("LC_MESSAGES", "de_DE.UTF-8", TRUE); setlocale (LC_MESSAGES, "de_DE.UTF-8"); /* Only do the test if translation is actually working... */ if (g_str_equal (dgettext ("test", "\"Unnamed\""), "\"Unbenannt\"")) @@ -802,7 +799,6 @@ test_l10n (void) g_test_skip ("translation is not working"); } - g_setenv ("LC_MESSAGES", locale, TRUE); setlocale (LC_MESSAGES, locale); g_free (locale); g_object_unref (settings); @@ -829,17 +825,14 @@ test_l10n_context (void) settings = g_settings_new ("org.gtk.test.localized"); - g_setenv ("LC_MESSAGES", "C", TRUE); setlocale (LC_MESSAGES, "C"); g_settings_get (settings, "backspace", "s", &str); - g_setenv ("LC_MESSAGES", locale, TRUE); setlocale (LC_MESSAGES, locale); g_assert_cmpstr (str, ==, "BackSpace"); g_free (str); str = NULL; - g_setenv ("LC_MESSAGES", "de_DE.UTF-8", TRUE); setlocale (LC_MESSAGES, "de_DE.UTF-8"); /* Only do the test if translation is actually working... */ if (g_str_equal (dgettext ("test", "\"Unnamed\""), "\"Unbenannt\"")) @@ -847,7 +840,6 @@ test_l10n_context (void) else g_test_skip ("translation is not working"); - g_setenv ("LC_MESSAGES", locale, TRUE); setlocale (LC_MESSAGES, locale); g_free (locale); g_object_unref (settings); @@ -871,17 +863,14 @@ test_l10n_time (void) settings = g_settings_new ("org.gtk.test.localized"); - g_setenv ("LC_TIME", "C", TRUE); setlocale (LC_TIME, "C"); str = g_settings_get_string (settings, "midnight"); - g_setenv ("LC_TIME", locale, TRUE); setlocale (LC_TIME, locale); g_assert_cmpstr (str, ==, "12:00 AM"); g_free (str); str = NULL; - g_setenv ("LC_TIME", "de_DE.UTF-8", TRUE); setlocale (LC_TIME, "de_DE.UTF-8"); /* Only do the test if translation is actually working... */ if (g_str_equal (dgettext ("test", "\"12:00 AM\""), "\"00:00\"")) @@ -897,7 +886,6 @@ test_l10n_time (void) g_test_skip ("translation is not working"); } - g_setenv ("LC_TIME", locale, TRUE); setlocale (LC_TIME, locale); g_free (locale); g_object_unref (settings); From 306d4567d2b055fef64fdc5e7119a36f205cc694 Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Mon, 24 Jul 2023 14:47:33 -0500 Subject: [PATCH 4/4] Replace setlocale() with uselocale() in GSettings l10n tests It's not safe to use setlocale() to mutate the locale in a threaded program. Lots of other tests still do this, and I'm not putting in the effort to fix them comprehensively in the absense of actual failures on CI, but I figured it'd be good to fix the tests that I was touching. --- gio/tests/gsettings.c | 129 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 25 deletions(-) diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c index d783ea6bd..aa30140ae 100644 --- a/gio/tests/gsettings.c +++ b/gio/tests/gsettings.c @@ -1,3 +1,5 @@ +#include "config.h" + #include #include #include @@ -10,6 +12,11 @@ #include "testenum.h" +#ifdef HAVE_XLOCALE_H +/* Needed on macOS and FreeBSD for uselocale() */ +#include +#endif + static const gchar *locale_dir = "."; static gboolean backend_set; @@ -747,7 +754,7 @@ test_atomic (void) * 1) The C library doesn't use LC_MESSAGES, that is implemented only * in libintl (defined in its ). * - * 2) The locale names that setlocale() accepts and returns aren't in + * 2) The locale names that uselocale() accepts and returns aren't in * the "de_DE" style, but like "German_Germany". * * 3) libintl looks at the Win32 thread locale and not the C library @@ -765,26 +772,46 @@ test_atomic (void) static void test_l10n (void) { +#ifndef HAVE_USELOCALE + g_test_skip ("Unsafe to change locale because platform does not support uselocale()"); +#else GSettings *settings; gchar *str; - gchar *locale; + locale_t original_locale; + locale_t new_locale; + locale_t result; bindtextdomain ("test", locale_dir); bind_textdomain_codeset ("test", "UTF-8"); - locale = g_strdup (setlocale (LC_MESSAGES, NULL)); + original_locale = uselocale ((locale_t) 0); + g_assert_true (original_locale != (locale_t) 0); + new_locale = newlocale (LC_MESSAGES_MASK, "C", (locale_t) 0); + g_assert_true (new_locale != (locale_t) 0); + result = uselocale (new_locale); + g_assert_true (result == original_locale); settings = g_settings_new ("org.gtk.test.localized"); - - setlocale (LC_MESSAGES, "C"); str = g_settings_get_string (settings, "error-message"); - setlocale (LC_MESSAGES, locale); + + result = uselocale (original_locale); + g_assert_true (result == new_locale); + freelocale (new_locale); g_assert_cmpstr (str, ==, "Unnamed"); g_free (str); str = NULL; - setlocale (LC_MESSAGES, "de_DE.UTF-8"); + new_locale = newlocale (LC_MESSAGES_MASK, "de_DE.UTF-8", (locale_t) 0); + if (new_locale == (locale_t) 0) + { + g_test_skip ("Cannot run test becaues de_DE.UTF-8 locale is not available"); + g_object_unref (settings); + return; + } + result = uselocale (new_locale); + g_assert_true (result == original_locale); + /* Only do the test if translation is actually working... */ if (g_str_equal (dgettext ("test", "\"Unnamed\""), "\"Unbenannt\"")) { @@ -799,9 +826,12 @@ test_l10n (void) g_test_skip ("translation is not working"); } - setlocale (LC_MESSAGES, locale); - g_free (locale); + result = uselocale (original_locale); + g_assert_true (result == new_locale); + freelocale (new_locale); + g_object_unref (settings); +#endif } /* Test that message context works as expected with translated @@ -814,44 +844,73 @@ test_l10n (void) static void test_l10n_context (void) { +#ifndef HAVE_USELOCALE + g_test_skip ("Unsafe to change locale because platform does not support uselocale()"); +#else GSettings *settings; gchar *str; - gchar *locale; + locale_t original_locale; + locale_t new_locale; + locale_t result; bindtextdomain ("test", locale_dir); bind_textdomain_codeset ("test", "UTF-8"); - locale = g_strdup (setlocale (LC_MESSAGES, NULL)); - settings = g_settings_new ("org.gtk.test.localized"); - setlocale (LC_MESSAGES, "C"); + original_locale = uselocale ((locale_t) 0); + g_assert_true (original_locale != (locale_t) 0); + new_locale = newlocale (LC_MESSAGES_MASK, "C", (locale_t) 0); + g_assert_true (new_locale != (locale_t) 0); + result = uselocale (new_locale); + g_assert_true (result == original_locale); + g_settings_get (settings, "backspace", "s", &str); - setlocale (LC_MESSAGES, locale); + + result = uselocale (original_locale); + g_assert_true (result == new_locale); + freelocale (new_locale); g_assert_cmpstr (str, ==, "BackSpace"); g_free (str); str = NULL; - setlocale (LC_MESSAGES, "de_DE.UTF-8"); + new_locale = newlocale (LC_MESSAGES_MASK, "de_DE.UTF-8", (locale_t) 0); + if (new_locale == (locale_t) 0) + { + g_test_skip ("Cannot run test becaues de_DE.UTF-8 locale is not available"); + g_object_unref (settings); + return; + } + result = uselocale (new_locale); + g_assert_true (result == original_locale); + /* Only do the test if translation is actually working... */ if (g_str_equal (dgettext ("test", "\"Unnamed\""), "\"Unbenannt\"")) settings_assert_cmpstr (settings, "backspace", ==, "Löschen"); else g_test_skip ("translation is not working"); - setlocale (LC_MESSAGES, locale); - g_free (locale); + result = uselocale (original_locale); + g_assert_true (result == new_locale); + freelocale (new_locale); + g_object_unref (settings); +#endif } /* Test use of l10n="time" and LC_TIME. */ static void test_l10n_time (void) { +#ifndef HAVE_USELOCALE + g_test_skip ("Unsafe to change locale because platform does not support uselocale()"); +#else GSettings *settings; gchar *str; - gchar *locale; + locale_t original_locale; + locale_t new_locale; + locale_t result; g_test_summary ("Test that l10n='time' attribute uses the correct category for translations"); g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2575"); @@ -859,19 +918,36 @@ test_l10n_time (void) bindtextdomain ("test", locale_dir); bind_textdomain_codeset ("test", "UTF-8"); - locale = g_strdup (setlocale (LC_TIME, NULL)); - settings = g_settings_new ("org.gtk.test.localized"); - setlocale (LC_TIME, "C"); + original_locale = uselocale ((locale_t) 0); + g_assert_true (original_locale != (locale_t) 0); + new_locale = duplocale (original_locale); + g_assert_true (new_locale != (locale_t) 0); + new_locale = newlocale (LC_TIME_MASK, "C", new_locale); + g_assert_true (new_locale != (locale_t) 0); + result = uselocale (new_locale); + g_assert_true (result == original_locale); + str = g_settings_get_string (settings, "midnight"); - setlocale (LC_TIME, locale); + + result = uselocale (original_locale); + g_assert_true (result == new_locale); g_assert_cmpstr (str, ==, "12:00 AM"); g_free (str); str = NULL; - setlocale (LC_TIME, "de_DE.UTF-8"); + new_locale = newlocale (LC_TIME_MASK, "de_DE.UTF-8", new_locale); + if (new_locale == (locale_t) 0) + { + g_test_skip ("Cannot run test becaues de_DE.UTF-8 locale is not available"); + g_object_unref (settings); + return; + } + result = uselocale (new_locale); + g_assert_true (result != (locale_t) 0); + /* Only do the test if translation is actually working... */ if (g_str_equal (dgettext ("test", "\"12:00 AM\""), "\"00:00\"")) { @@ -886,9 +962,12 @@ test_l10n_time (void) g_test_skip ("translation is not working"); } - setlocale (LC_TIME, locale); - g_free (locale); + result = uselocale (original_locale); + g_assert_true (result == new_locale); + freelocale (new_locale); + g_object_unref (settings); +#endif } enum