diff --git a/app/flatpak-builtins-config.c b/app/flatpak-builtins-config.c index eb5a8e9a..6fb40952 100644 --- a/app/flatpak-builtins-config.c +++ b/app/flatpak-builtins-config.c @@ -63,12 +63,79 @@ looks_like_a_language (const char *s) return TRUE; } +static gboolean +looks_like_a_region (const char *s) +{ + g_auto(GStrv) locale = g_strsplit (s, "_", 3); + int i; + int len; + + if (!looks_like_a_language(locale[0])) + return FALSE; + + if (locale[1] != NULL) + { + len = strlen (locale[1]); + + // This can be either GB or Latn + if (len < 2 || len > 4) + return FALSE; + + for (i = 0; i < len; i++) + { + if ((locale[2] == NULL) && (!g_ascii_isalpha (locale[1][i]) || !g_ascii_isupper (locale[1][i]))) + return FALSE; + else if (!g_ascii_isalpha (locale[1][i])) + return FALSE; + } + } + + if (g_strv_length (locale) > 2) + { + len = strlen (locale[2]); + + if (len < 2 || len > 3) + return FALSE; + + for (i = 0; i < len; i++) + { + if (!g_ascii_isalpha (locale[2][i]) || !g_ascii_isupper (locale[2][i])) + return FALSE; + } + } + + return TRUE; +} + +static char * +parse_locale (const char *value, GError **error) +{ + g_auto(GStrv) strs = NULL; + int i; + + strs = g_strsplit (value, ";", 0); + for (i = 0; strs[i]; i++) + { + if (!looks_like_a_language (strs[i]) && !looks_like_a_region (strs[i])) + { + flatpak_fail (error, _("'%s' does not look like a language/locale code"), strs[i]); + return NULL; + } + } + + return g_strdup (value); +} + static char * parse_lang (const char *value, GError **error) { g_auto(GStrv) strs = NULL; int i; + if (strcmp (value, "*") == 0 || + strcmp (value, "*all*") == 0) + return g_strdup (""); + strs = g_strsplit (value, ";", 0); for (i = 0; strs[i]; i++) { @@ -83,27 +150,17 @@ parse_lang (const char *value, GError **error) } static char * -parse_special_lang (const char *value, GError **error) -{ - if (strcmp (value, "*") == 0 || - strcmp (value, "*all*") == 0) - return g_strdup (""); - - return parse_lang (value, error); -} - -static char * -print_lang (const char *value) +print_locale (const char *value) { return g_strdup (value); } static char * -print_special_lang (const char *value) +print_lang (const char *value) { if (*value == 0) return g_strdup ("*all*"); - return print_lang (value); + return g_strdup (value); } static char * @@ -123,8 +180,8 @@ typedef struct } ConfigKey; ConfigKey keys[] = { - { "languages", parse_special_lang, print_special_lang, get_lang_default }, - { "extra-languages", parse_lang, print_lang, NULL }, + { "languages", parse_lang, print_lang, get_lang_default }, + { "extra-languages", parse_locale, print_locale, NULL }, }; static ConfigKey * diff --git a/common/flatpak-dir-private.h b/common/flatpak-dir-private.h index 205d4e9b..2aca7adf 100644 --- a/common/flatpak-dir-private.h +++ b/common/flatpak-dir-private.h @@ -955,7 +955,9 @@ gboolean flatpak_dir_resolve_p2p_refs (FlatpakDir *self, GError **error); +char ** flatpak_dir_get_default_locales (FlatpakDir *self); char ** flatpak_dir_get_default_locale_languages (FlatpakDir *self); +char ** flatpak_dir_get_locales (FlatpakDir *self); char ** flatpak_dir_get_locale_languages (FlatpakDir *self); char ** flatpak_dir_get_locale_subpaths (FlatpakDir *self); diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c index 2a89c4ef..6a68da43 100644 --- a/common/flatpak-dir.c +++ b/common/flatpak-dir.c @@ -14561,20 +14561,13 @@ flatpak_dir_get_config_strv (FlatpakDir *self, char *key) return NULL; } -char ** -flatpak_dir_get_default_locale_languages (FlatpakDir *self) +static void +get_system_locales (FlatpakDir *self, GPtrArray *langs) { - g_autoptr(GPtrArray) langs = g_ptr_array_new_with_free_func (g_free); g_autoptr(GDBusProxy) localed_proxy = NULL; g_autoptr(GDBusProxy) accounts_proxy = NULL; - g_auto(GStrv) extra_languages = NULL; - extra_languages = flatpak_dir_get_config_strv (self, "xa.extra-languages"); - - if (flatpak_dir_is_user (self)) - return sort_strv (flatpak_strv_merge (extra_languages, flatpak_get_current_locale_langs ())); - - /* Then get the system default locales */ + /* Get the system default locales */ localed_proxy = get_localed_dbus_proxy (); if (localed_proxy != NULL) get_locale_langs_from_localed_dbus (localed_proxy, langs); @@ -14584,12 +14577,67 @@ flatpak_dir_get_default_locale_languages (FlatpakDir *self) accounts_proxy = get_accounts_dbus_proxy (); if (accounts_proxy != NULL) get_locale_langs_from_accounts_dbus (accounts_proxy, langs); - g_ptr_array_add (langs, NULL); +} + +char ** +flatpak_dir_get_default_locales (FlatpakDir *self) +{ + g_autoptr(GPtrArray) langs = g_ptr_array_new_with_free_func (g_free); + g_auto(GStrv) extra_languages = NULL; + + extra_languages = flatpak_dir_get_config_strv (self, "xa.extra-languages"); + + if (flatpak_dir_is_user (self)) + return sort_strv (flatpak_strv_merge (extra_languages, flatpak_get_current_locale_langs ())); + + /* Then get the system default locales */ + get_system_locales (self, langs); return sort_strv (flatpak_strv_merge (extra_languages, (char **) langs->pdata)); } +char ** +flatpak_dir_get_default_locale_languages (FlatpakDir *self) +{ + g_autoptr(GPtrArray) langs = g_ptr_array_new_with_free_func (g_free); + g_auto(GStrv) extra_languages = NULL; + int i; + + extra_languages = flatpak_dir_get_config_strv (self, "xa.extra-languages"); + for (i = 0; extra_languages != NULL && extra_languages[i] != NULL; i++) + { + g_auto(GStrv) locales = g_strsplit (extra_languages[i], "_", -1); + g_free (extra_languages[i]); + extra_languages[i] = g_strdup (locales[0]); + } + + if (flatpak_dir_is_user (self)) + return sort_strv (flatpak_strv_merge (extra_languages, flatpak_get_current_locale_langs ())); + + /* Then get the system default locales */ + get_system_locales (self, langs); + + return sort_strv (flatpak_strv_merge (extra_languages, (char **) langs->pdata)); +} + +char ** +flatpak_dir_get_locales (FlatpakDir *self) +{ + char **langs = NULL; + + /* Fetch the list of languages specified by xa.languages - if this key is empty, + * this would mean that all languages are accepted. You can read the man for the + * flatpak-config section for more info. + */ + langs = flatpak_dir_get_config_strv (self, "xa.languages"); + if (langs) + return sort_strv (langs); + + return flatpak_dir_get_default_locales (self); +} + + char ** flatpak_dir_get_locale_languages (FlatpakDir *self) { diff --git a/common/flatpak-installation.c b/common/flatpak-installation.c index 42e2af41..ad730671 100644 --- a/common/flatpak-installation.c +++ b/common/flatpak-installation.c @@ -1682,6 +1682,32 @@ flatpak_installation_get_default_languages (FlatpakInstallation *self, return flatpak_dir_get_locale_languages (dir); } +/** + * flatpak_installation_get_default_locales: + * @self: a #FlatpakInstallation + * @error: return location for a #GError + * + * Like flatpak_installation_get_default_languages() but includes region + * information (e.g. en_US rather than en) which may be included in the + * xa.extra-languages configuration. + * + * Returns: (array zero-terminated=1) (element-type utf8) (transfer full): + * A possibly empty array of language and locale strings, or %NULL on error. + * Since: 1.5.1 + */ +char ** +flatpak_installation_get_default_locales (FlatpakInstallation *self, + GError **error) +{ + g_autoptr(FlatpakDir) dir = NULL; + + dir = flatpak_installation_get_dir (self, error); + if (dir == NULL) + return NULL; + + return flatpak_dir_get_locales (dir); +} + /** * flatpak_installation_get_min_free_space_bytes: * @self: a #FlatpakInstallation diff --git a/common/flatpak-installation.h b/common/flatpak-installation.h index c97070ce..3baabad6 100644 --- a/common/flatpak-installation.h +++ b/common/flatpak-installation.h @@ -298,6 +298,8 @@ FLATPAK_EXTERN char * flatpak_installation_get_config (FlatpakIns GError **error); FLATPAK_EXTERN char ** flatpak_installation_get_default_languages (FlatpakInstallation *self, GError **error); +FLATPAK_EXTERN char ** flatpak_installation_get_default_locales (FlatpakInstallation *self, + GError **error); FLATPAK_EXTERN char * flatpak_installation_load_app_overrides (FlatpakInstallation *self, const char *app_id, GCancellable *cancellable, diff --git a/doc/flatpak-config.xml b/doc/flatpak-config.xml index 5d05cb54..32d5420e 100644 --- a/doc/flatpak-config.xml +++ b/doc/flatpak-config.xml @@ -70,7 +70,8 @@ This key is used when languages is not set, and it defines extra locale extensions on top of the system configured languages. The value is a - semicolon-separated list of two-letter language codes. + semicolon-separated list of two-letter language codes or locale identifiers + (eg. en;en_DK;az_Latn_AZ). diff --git a/tests/testlibrary.c b/tests/testlibrary.c index 0e017aed..c0cc4217 100644 --- a/tests/testlibrary.c +++ b/tests/testlibrary.c @@ -253,6 +253,22 @@ clean_languages (void) run_test_subprocess (argv, RUN_TEST_SUBPROCESS_DEFAULT); } +static void +configure_extra_languages (void) +{ + char *argv[] = { "flatpak", "config", "--user", "--set", "extra-languages", "de_DE", NULL }; + + run_test_subprocess (argv, RUN_TEST_SUBPROCESS_DEFAULT); +} + +static void +clean_extra_languages (void) +{ + char *argv[] = { "flatpak", "config", "--user", "--unset", "extra-languages", NULL }; + + run_test_subprocess (argv, RUN_TEST_SUBPROCESS_DEFAULT); +} + static void test_languages_config (void) { @@ -273,18 +289,31 @@ test_languages_config (void) value = flatpak_installation_get_default_languages (inst, &error); g_assert_no_error (error); g_assert_cmpstr (value[0], ==, "en"); + g_assert_null (value[1]); - res = flatpak_installation_set_config_sync (inst, "extra-languages", "en;pt", NULL, &error); + res = flatpak_installation_set_config_sync (inst, "extra-languages", "pt_BR;az_Latn_AZ;es", NULL, &error); g_assert_no_error (error); g_assert_true (res); value = flatpak_installation_get_default_languages (inst, &error); g_assert_no_error (error); - g_assert_cmpstr (value[0], ==, "en"); - g_assert_cmpstr (value[1], ==, "pt"); - g_assert_null (value[2]); + g_assert_cmpstr (value[0], ==, "az"); + g_assert_cmpstr (value[1], ==, "en"); + g_assert_cmpstr (value[2], ==, "es"); + g_assert_cmpstr (value[3], ==, "pt"); + g_assert_null (value[4]); - g_clear_pointer (&value, g_free); + g_clear_pointer (&value, g_strfreev); + + value = flatpak_installation_get_default_locales (inst, &error); + g_assert_no_error (error); + g_assert_cmpstr (value[0], ==, "az_Latn_AZ"); + g_assert_cmpstr (value[1], ==, "en"); + g_assert_cmpstr (value[2], ==, "es"); + g_assert_cmpstr (value[3], ==, "pt_BR"); + g_assert_null (value[4]); + + g_clear_pointer (&value, g_strfreev); res = flatpak_installation_set_config_sync (inst, "languages", "ar;es", NULL, &error); g_assert_no_error (error); @@ -296,7 +325,8 @@ test_languages_config (void) g_assert_cmpstr (value[1], ==, "es"); g_assert_null (value[2]); - g_clear_pointer (&value, g_free); + g_clear_pointer (&value, g_strfreev); + clean_extra_languages (); configure_languages (); } @@ -1083,6 +1113,45 @@ test_list_remote_related_refs (void) g_assert_true (should_download); g_assert_true (should_delete); g_assert_false (should_autoprune); + + // Make the test with extra-languages, instead of languages + clean_languages(); + configure_extra_languages(); + + inst = flatpak_installation_new_user (NULL, &error); + g_assert_no_error (error); + + refs = flatpak_installation_list_remote_related_refs_sync (inst, repo_name, app, NULL, &error); + g_assert_nonnull (refs); + g_assert_no_error (error); + + g_assert_cmpint (refs->len, ==, 1); + ref = g_ptr_array_index (refs, 0); + + g_assert_cmpstr (flatpak_ref_get_name (FLATPAK_REF (ref)), ==, "org.test.Hello.Locale"); + g_assert_true (flatpak_related_ref_should_download (ref)); + g_assert_true (flatpak_related_ref_should_delete (ref)); + g_assert_false (flatpak_related_ref_should_autoprune (ref)); + g_assert (g_strv_length ((char **) flatpak_related_ref_get_subpaths (ref)) == 2); + g_assert_cmpstr (flatpak_related_ref_get_subpaths (ref)[0], ==, "/de"); + g_assert_cmpstr (flatpak_related_ref_get_subpaths (ref)[1], ==, "/en"); + + g_object_get (ref, + "subpaths", &subpaths, + "should-download", &should_download, + "should-delete", &should_delete, + "should-autoprune", &should_autoprune, + NULL); + + g_assert (g_strv_length (subpaths) == 2); + g_assert_cmpstr (subpaths[0], ==, "/de"); + g_assert_cmpstr (subpaths[1], ==, "/en"); + g_assert_true (should_download); + g_assert_true (should_delete); + g_assert_false (should_autoprune); + + configure_languages(); + clean_extra_languages(); } static void @@ -3578,6 +3647,59 @@ test_list_installed_related_refs (void) g_assert_false (flatpak_related_ref_should_autoprune (ref)); g_assert (g_strv_length ((char **) flatpak_related_ref_get_subpaths (ref)) == 1); g_assert_cmpstr (flatpak_related_ref_get_subpaths (ref)[0], ==, "/de"); + + // Make the test with extra-languages, instead of languages + clean_languages(); + configure_extra_languages(); + empty_installation (inst); + + refs = flatpak_installation_list_installed_related_refs_sync (inst, repo_name, app, NULL, &error); + g_assert_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED); + g_assert_null (refs); + g_clear_error (&error); + + iref = flatpak_installation_install (inst, repo_name, FLATPAK_REF_KIND_APP, "org.test.Hello", NULL, "master", NULL, NULL, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (iref); + g_clear_object (&iref); + + refs = flatpak_installation_list_installed_related_refs_sync (inst, repo_name, app, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (refs); + g_assert_cmpint (refs->len, ==, 0); + g_clear_pointer (&refs, g_ptr_array_unref); + + transaction = flatpak_transaction_new_for_installation (inst, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (transaction); + + res = flatpak_transaction_add_update (transaction, app, NULL, NULL, &error); + g_assert_no_error (error); + g_assert_true (res); + + flatpak_transaction_run (transaction, NULL, &error); + g_assert_no_error (error); + g_assert_true (res); + + g_clear_object (&transaction); + + refs = flatpak_installation_list_installed_related_refs_sync (inst, repo_name, app, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (refs); + g_assert_cmpint (refs->len, ==, 1); + + ref = g_ptr_array_index (refs, 0); + + g_assert_cmpstr (flatpak_ref_get_name (FLATPAK_REF (ref)), ==, "org.test.Hello.Locale"); + g_assert_true (flatpak_related_ref_should_download (ref)); + g_assert_true (flatpak_related_ref_should_delete (ref)); + g_assert_false (flatpak_related_ref_should_autoprune (ref)); + g_assert (g_strv_length ((char **) flatpak_related_ref_get_subpaths (ref)) == 2); + g_assert_cmpstr (flatpak_related_ref_get_subpaths (ref)[0], ==, "/de"); + g_assert_cmpstr (flatpak_related_ref_get_subpaths (ref)[1], ==, "/en"); + + configure_languages(); + clean_extra_languages(); } static void