Fix invalid Unix locale to Flutter locale (BCP-47) mapping (#36512)
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
diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc
index 896eef7..a15e816 100644
--- a/shell/platform/linux/fl_engine.cc
+++ b/shell/platform/linux/fl_engine.cc
@@ -86,29 +86,37 @@
// 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 @@
// 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<FlutterLocale*>(g_malloc0(sizeof(FlutterLocale)));
@@ -139,8 +141,8 @@
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<FlutterLocale**>(locales_array->pdata);
diff --git a/shell/platform/linux/fl_engine_test.cc b/shell/platform/linux/fl_engine_test.cc
index 6bbc699..727f76d 100644
--- a/shell/platform/linux/fl_engine_test.cc
+++ b/shell/platform/linux/fl_engine_test.cc
@@ -359,4 +359,57 @@
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<size_t>(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)