From 8b3eb490e494fe4c5c7536a0db823a932d6b5fad Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Fri, 30 Sep 2022 10:46:17 +1300 Subject: [PATCH] Fix invalid Unix locale to Flutter locale (BCP-47) mapping Flutter locales use the BCP-47 format (https://www.rfc-editor.org/rfc/bcp/bcp47.txt), and Unix locales use a POSIX format. There is not a perfect mapping between them, see https://wiki.openoffice.org/wiki/LocaleMapping The current implementation is not correctly setting the appropriate optional fields, so remove that. There are likely some Unix locales that should set these fields, but these are best fixed by adding special cases for them as they are discovered. Fixes: https://github.com/flutter/flutter/issues/111341 --- shell/platform/linux/fl_engine.cc | 36 ++++++++--------- shell/platform/linux/fl_engine_test.cc | 53 ++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 17 deletions(-) diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index 896eef701fc8e..a15e81672881f 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -86,29 +86,37 @@ static void parse_locale(const gchar* locale, // Locales are in the form "language[_territory][.codeset][@modifier]" gchar* match = strrchr(l, '@'); if (match != nullptr) { - *modifier = g_strdup(match + 1); + if (modifier != nullptr) { + *modifier = g_strdup(match + 1); + } *match = '\0'; - } else { + } else if (modifier != nullptr) { *modifier = nullptr; } match = strrchr(l, '.'); if (match != nullptr) { - *codeset = g_strdup(match + 1); + if (codeset != nullptr) { + *codeset = g_strdup(match + 1); + } *match = '\0'; - } else { + } else if (codeset != nullptr) { *codeset = nullptr; } match = strrchr(l, '_'); if (match != nullptr) { - *territory = g_strdup(match + 1); + if (territory != nullptr) { + *territory = g_strdup(match + 1); + } *match = '\0'; - } else { + } else if (territory != nullptr) { *territory = nullptr; } - *language = l; + if (language != nullptr) { + *language = l; + } } // Passes locale information to the Flutter engine. @@ -118,20 +126,14 @@ static void setup_locales(FlEngine* self) { // Helper array to take ownership of the strings passed to Flutter. g_autoptr(GPtrArray) locale_strings = g_ptr_array_new_with_free_func(g_free); for (int i = 0; languages[i] != nullptr; i++) { - gchar *language, *territory, *codeset, *modifier; - parse_locale(languages[i], &language, &territory, &codeset, &modifier); + gchar *language, *territory; + parse_locale(languages[i], &language, &territory, nullptr, nullptr); if (language != nullptr) { g_ptr_array_add(locale_strings, language); } if (territory != nullptr) { g_ptr_array_add(locale_strings, territory); } - if (codeset != nullptr) { - g_ptr_array_add(locale_strings, codeset); - } - if (modifier != nullptr) { - g_ptr_array_add(locale_strings, modifier); - } FlutterLocale* locale = static_cast(g_malloc0(sizeof(FlutterLocale))); @@ -139,8 +141,8 @@ static void setup_locales(FlEngine* self) { locale->struct_size = sizeof(FlutterLocale); locale->language_code = language; locale->country_code = territory; - locale->script_code = codeset; - locale->variant_code = modifier; + locale->script_code = nullptr; + locale->variant_code = nullptr; } FlutterLocale** locales = reinterpret_cast(locales_array->pdata); diff --git a/shell/platform/linux/fl_engine_test.cc b/shell/platform/linux/fl_engine_test.cc index 6bbc699195d5e..727f76d61a902 100644 --- a/shell/platform/linux/fl_engine_test.cc +++ b/shell/platform/linux/fl_engine_test.cc @@ -359,4 +359,57 @@ TEST(FlEngineTest, DartEntrypointArgs) { EXPECT_TRUE(called); } +TEST(FlEngineTest, Locales) { + gchar* initial_language = g_strdup(g_getenv("LANGUAGE")); + g_setenv("LANGUAGE", "de:en_US", TRUE); + g_autoptr(FlDartProject) project = fl_dart_project_new(); + + g_autoptr(FlEngine) engine = make_mock_engine_with_project(project); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + + bool called = false; + embedder_api->UpdateLocales = MOCK_ENGINE_PROC( + UpdateLocales, ([&called](auto engine, const FlutterLocale** locales, + size_t locales_count) { + called = true; + + EXPECT_EQ(locales_count, static_cast(4)); + + EXPECT_STREQ(locales[0]->language_code, "de"); + EXPECT_STREQ(locales[0]->country_code, nullptr); + EXPECT_STREQ(locales[0]->script_code, nullptr); + EXPECT_STREQ(locales[0]->variant_code, nullptr); + + EXPECT_STREQ(locales[1]->language_code, "en"); + EXPECT_STREQ(locales[1]->country_code, "US"); + EXPECT_STREQ(locales[1]->script_code, nullptr); + EXPECT_STREQ(locales[1]->variant_code, nullptr); + + EXPECT_STREQ(locales[2]->language_code, "en"); + EXPECT_STREQ(locales[2]->country_code, nullptr); + EXPECT_STREQ(locales[2]->script_code, nullptr); + EXPECT_STREQ(locales[2]->variant_code, nullptr); + + EXPECT_STREQ(locales[3]->language_code, "C"); + EXPECT_STREQ(locales[3]->country_code, nullptr); + EXPECT_STREQ(locales[3]->script_code, nullptr); + EXPECT_STREQ(locales[3]->variant_code, nullptr); + + return kSuccess; + })); + + g_autoptr(GError) error = nullptr; + EXPECT_TRUE(fl_engine_start(engine, &error)); + EXPECT_EQ(error, nullptr); + + EXPECT_TRUE(called); + + if (initial_language) { + g_setenv("LANGUAGE", initial_language, TRUE); + } else { + g_unsetenv("LANGUAGE"); + } + g_free(initial_language); +} + // NOLINTEND(clang-analyzer-core.StackAddressEscape)