-
Notifications
You must be signed in to change notification settings - Fork 6k
[embedder][glfw] Add support for locales to glfw shell #22657
Changes from 4 commits
efc980a
d3c3dab
85117d5
ebd9dbd
7fbf3ac
a4fc6db
656bfa6
2d0a87f
d38ec35
280b5f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,11 +12,14 @@ | |
| #include <cstdlib> | ||
| #include <filesystem> | ||
| #include <iostream> | ||
| #include <list> | ||
| #include <string> | ||
|
|
||
| #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h" | ||
| #include "flutter/shell/platform/common/cpp/incoming_message_dispatcher.h" | ||
| #include "flutter/shell/platform/common/cpp/path_utils.h" | ||
| #include "flutter/shell/platform/embedder/embedder.h" | ||
| #include "flutter/shell/platform/glfw/flutter_glfw_private.h" | ||
| #include "flutter/shell/platform/glfw/glfw_event_loop.h" | ||
| #include "flutter/shell/platform/glfw/headless_event_loop.h" | ||
| #include "flutter/shell/platform/glfw/key_event_handler.h" | ||
|
|
@@ -690,6 +693,123 @@ static bool RunFlutterEngine( | |
| return true; | ||
| } | ||
|
|
||
| const char* GetLocaleStringFromEnvironmentVariables(const char* language, | ||
|
rokob marked this conversation as resolved.
Outdated
|
||
| const char* lc_all, | ||
| const char* lc_messages, | ||
| const char* lang) { | ||
| const char* retval; | ||
| retval = language; | ||
| if ((retval != NULL) && (retval[0] != '\0')) { | ||
| return retval; | ||
| } | ||
| retval = lc_all; | ||
| if ((retval != NULL) && (retval[0] != '\0')) { | ||
| return retval; | ||
| } | ||
| retval = lc_messages; | ||
| if ((retval != NULL) && (retval[0] != '\0')) { | ||
| return retval; | ||
| } | ||
| retval = lang; | ||
| if ((retval != NULL) && (retval[0] != '\0')) { | ||
| return retval; | ||
| } | ||
|
|
||
| return NULL; | ||
| } | ||
|
|
||
| const char* GetLocaleStringFromEnvironment() { | ||
|
rokob marked this conversation as resolved.
Outdated
|
||
| return GetLocaleStringFromEnvironmentVariables( | ||
| getenv("LANGUAGE"), getenv("LC_ALL"), getenv("LC_MESSAGES"), | ||
| getenv("LANG")); | ||
| } | ||
|
|
||
| // Parse a locale into its components. | ||
|
rokob marked this conversation as resolved.
Outdated
|
||
| void ParseLocale(const std::string& locale, | ||
| std::string* language, | ||
| std::string* territory, | ||
| std::string* codeset, | ||
| std::string* modifier) { | ||
|
rokob marked this conversation as resolved.
Outdated
|
||
| // Locales are in the form "language[_territory][.codeset][@modifier]" | ||
| std::string::size_type end = locale.size(); | ||
| std::string::size_type modifier_pos = locale.rfind('@'); | ||
| if (modifier_pos != std::string::npos) { | ||
| *modifier = locale.substr(modifier_pos + 1, end - modifier_pos - 1); | ||
| end = modifier_pos; | ||
| } | ||
|
|
||
| std::string::size_type codeset_pos = locale.rfind('.'); | ||
| if (codeset_pos != std::string::npos) { | ||
| *codeset = locale.substr(codeset_pos + 1, end - codeset_pos - 1); | ||
| end = codeset_pos; | ||
| } | ||
|
|
||
| std::string::size_type territory_pos = locale.rfind('_'); | ||
| if (territory_pos != std::string::npos) { | ||
| *territory = locale.substr(territory_pos + 1, end - territory_pos - 1); | ||
| end = territory_pos; | ||
| } | ||
|
|
||
| *language = locale.substr(0, end); | ||
| } | ||
|
|
||
| std::vector<std::unique_ptr<FlutterLocale>> GetLocales( | ||
| const char* locale_string, | ||
| std::list<std::string>& locale_storage) { | ||
| if (!locale_string || locale_string[0] == '\0') { | ||
| locale_string = "C"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found somewhere in some posix docs about locales that "ISO C says that all programs start by default in the standard ‘C’ locale." And looking around at other things that parse these strings, they use "C" as the fallback. |
||
| } | ||
| std::istringstream locales_stream(locale_string); | ||
| std::vector<std::unique_ptr<FlutterLocale>> locales; | ||
| std::string s; | ||
| while (getline(locales_stream, s, ':')) { | ||
| locales.push_back(std::make_unique<FlutterLocale>()); | ||
| std::string language, territory, codeset, modifier; | ||
| ParseLocale(s, &language, &territory, &codeset, &modifier); | ||
| FlutterLocale* locale = locales.back().get(); | ||
| locale->struct_size = sizeof(FlutterLocale); | ||
| if (!language.empty()) { | ||
| locale_storage.push_back(language); | ||
| locale->language_code = locale_storage.back().c_str(); | ||
| } | ||
| if (!territory.empty()) { | ||
| locale_storage.push_back(territory); | ||
| locale->country_code = locale_storage.back().c_str(); | ||
| } | ||
| if (!codeset.empty()) { | ||
| locale_storage.push_back(codeset); | ||
| locale->script_code = locale_storage.back().c_str(); | ||
| } | ||
| if (!modifier.empty()) { | ||
| locale_storage.push_back(modifier); | ||
| locale->variant_code = locale_storage.back().c_str(); | ||
| } | ||
| } | ||
| return locales; | ||
| } | ||
|
|
||
| // Returns parsed locales fetched from the environment. | ||
| std::vector<std::unique_ptr<FlutterLocale>> GetLocalesFromEnvironment( | ||
|
rokob marked this conversation as resolved.
Outdated
|
||
| std::list<std::string>& locale_storage) { | ||
| return GetLocales(GetLocaleStringFromEnvironment(), locale_storage); | ||
| } | ||
|
|
||
| // Passes locale information to the Flutter engine. | ||
| static void SetUpLocales(FlutterDesktopEngineState* state) { | ||
| // Helper to extend lifetime of the strings passed to Flutter. | ||
| std::list<std::string> locale_storage; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This construction seems more confusing than just inlining the conversion to C strings (as we do on other platforms IIRC).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need something to extend the lifetimes based on the calling convention, and this is basically how the other linux shell does it. I didn't think to look at the windows code, that is a much better way to handle it, I will mirror that over here. |
||
| std::vector<std::unique_ptr<FlutterLocale>> locales = | ||
| GetLocalesFromEnvironment(locale_storage); | ||
| FlutterLocale** locales_array = | ||
| reinterpret_cast<FlutterLocale**>(&locales[0]); | ||
| FlutterEngineResult result = FlutterEngineUpdateLocales( | ||
| state->flutter_engine, const_cast<const FlutterLocale**>(locales_array), | ||
| locales.size()); | ||
| if (result != kSuccess) { | ||
| std::cerr << "Failed to set up Flutter locales." << std::endl; | ||
| } | ||
| } | ||
|
|
||
| // Populates |state|'s helper object fields that are common to normal and | ||
| // headless mode. | ||
| // | ||
|
|
@@ -713,6 +833,8 @@ static void SetUpCommonEngineState(FlutterDesktopEngineState* state, | |
| // System channel handler. | ||
| state->platform_handler = std::make_unique<flutter::PlatformHandler>( | ||
| state->internal_plugin_registrar->messenger(), window); | ||
|
|
||
| SetUpLocales(state); | ||
| } | ||
|
|
||
| bool FlutterDesktopInit() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| #ifndef FLUTTER_SHELL_PLATFORM_GLFW_FLUTTER_GLFW_PRIVATE_H_ | ||
| #define FLUTTER_SHELL_PLATFORM_GLFW_FLUTTER_GLFW_PRIVATE_H_ | ||
|
|
||
| #include <list> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "flutter/shell/platform/embedder/embedder.h" | ||
|
|
||
| const char* GetLocaleStringFromEnvironmentVariables(const char* language, | ||
| const char* lc_all, | ||
| const char* lc_messages, | ||
| const char* lang); | ||
|
|
||
| void ParseLocale(const std::string& locale, | ||
| std::string* language, | ||
| std::string* territory, | ||
| std::string* codeset, | ||
| std::string* modifier); | ||
|
|
||
| std::vector<std::unique_ptr<FlutterLocale>> GetLocales( | ||
| const char* locale_string, | ||
| std::list<std::string>& locale_storage); | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason there are multiple exposed helpers on Windows is that we can't easily mock out what the OS returns; that's not the case here, since you can easily set environment variables in tests, so I would expect there to only be one visible function for getting locales even if it has helper functions internally.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was trying to not set environment variables in tests and instead make more pure functions for testability, but that is more noise than its worth here so I will collapse things. |
||
| #endif // FLUTTER_SHELL_PLATFORM_GLFW_FLUTTER_GLFW_PRIVATE_H_ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| #include "flutter/shell/platform/glfw/flutter_glfw_private.h" | ||
|
|
||
| #include "gtest/gtest.h" | ||
|
|
||
| TEST(FlutterGlfwTest, GetLocaleStringFromEnvironment) { | ||
| EXPECT_EQ( | ||
| GetLocaleStringFromEnvironmentVariables("sv:de", NULL, NULL, "sv_SE"), | ||
| "sv:de"); | ||
| EXPECT_EQ( | ||
| GetLocaleStringFromEnvironmentVariables(NULL, "en_EN", NULL, "sv_SE"), | ||
| "en_EN"); | ||
| EXPECT_EQ( | ||
| GetLocaleStringFromEnvironmentVariables(NULL, NULL, "de_DE", "sv_SE"), | ||
| "de_DE"); | ||
| EXPECT_EQ(GetLocaleStringFromEnvironmentVariables(NULL, NULL, NULL, "sv_SE"), | ||
| "sv_SE"); | ||
| EXPECT_EQ(GetLocaleStringFromEnvironmentVariables(NULL, NULL, NULL, NULL), | ||
| nullptr); | ||
| } | ||
|
|
||
| TEST(FlutterGlfwTest, ParseLocaleSimple) { | ||
| std::string s = "en"; | ||
| std::string language, territory, codeset, modifier; | ||
| ParseLocale(s, &language, &territory, &codeset, &modifier); | ||
| EXPECT_EQ(language, "en"); | ||
| EXPECT_EQ(territory, ""); | ||
| EXPECT_EQ(codeset, ""); | ||
| EXPECT_EQ(modifier, ""); | ||
| } | ||
|
|
||
| TEST(FlutterGlfwTest, ParseLocaleWithTerritory) { | ||
| std::string s = "en_GB"; | ||
| std::string language, territory, codeset, modifier; | ||
| ParseLocale(s, &language, &territory, &codeset, &modifier); | ||
| EXPECT_EQ(language, "en"); | ||
| EXPECT_EQ(territory, "GB"); | ||
| EXPECT_EQ(codeset, ""); | ||
| EXPECT_EQ(modifier, ""); | ||
| } | ||
|
|
||
| TEST(FlutterGlfwTest, ParseLocaleWithCodeset) { | ||
| std::string s = "zh_CN.UTF-8"; | ||
| std::string language, territory, codeset, modifier; | ||
| ParseLocale(s, &language, &territory, &codeset, &modifier); | ||
| EXPECT_EQ(language, "zh"); | ||
| EXPECT_EQ(territory, "CN"); | ||
| EXPECT_EQ(codeset, "UTF-8"); | ||
| EXPECT_EQ(modifier, ""); | ||
| } | ||
|
|
||
| TEST(FlutterGlfwTest, ParseLocaleFull) { | ||
| std::string s = "en_GB.ISO-8859-1@euro"; | ||
| std::string language, territory, codeset, modifier; | ||
| ParseLocale(s, &language, &territory, &codeset, &modifier); | ||
| EXPECT_EQ(language, "en"); | ||
| EXPECT_EQ(territory, "GB"); | ||
| EXPECT_EQ(codeset, "ISO-8859-1"); | ||
| EXPECT_EQ(modifier, "euro"); | ||
| } | ||
|
|
||
| TEST(FlutterGlfwTest, GetLocalesSimple) { | ||
| const char* locale_string = "en_US"; | ||
| std::list<std::string> locale_storage; | ||
|
|
||
| std::vector<std::unique_ptr<FlutterLocale>> locales = | ||
| GetLocales(locale_string, locale_storage); | ||
|
|
||
| EXPECT_EQ(locales.size(), 1UL); | ||
|
|
||
| EXPECT_STREQ(locales[0]->language_code, "en"); | ||
| EXPECT_STREQ(locales[0]->country_code, "US"); | ||
| EXPECT_STREQ(locales[0]->script_code, nullptr); | ||
| EXPECT_STREQ(locales[0]->variant_code, nullptr); | ||
| } | ||
|
|
||
| TEST(FlutterGlfwTest, GetLocalesFull) { | ||
| const char* locale_string = "en_GB.ISO-8859-1@euro:en_US:sv:zh_CN.UTF-8"; | ||
| std::list<std::string> locale_storage; | ||
|
|
||
| std::vector<std::unique_ptr<FlutterLocale>> locales = | ||
| GetLocales(locale_string, locale_storage); | ||
|
|
||
| EXPECT_EQ(locales.size(), 4UL); | ||
|
|
||
| EXPECT_STREQ(locales[0]->language_code, "en"); | ||
| EXPECT_STREQ(locales[0]->country_code, "GB"); | ||
| EXPECT_STREQ(locales[0]->script_code, "ISO-8859-1"); | ||
| EXPECT_STREQ(locales[0]->variant_code, "euro"); | ||
|
|
||
| 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, "sv"); | ||
| 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, "zh"); | ||
| EXPECT_STREQ(locales[3]->country_code, "CN"); | ||
| EXPECT_STREQ(locales[3]->script_code, "UTF-8"); | ||
| EXPECT_STREQ(locales[3]->variant_code, nullptr); | ||
| } | ||
|
|
||
| TEST(FlutterGlfwTest, GetLocalesEmpty) { | ||
| const char* locale_string = ""; | ||
| std::list<std::string> locale_storage; | ||
|
|
||
| std::vector<std::unique_ptr<FlutterLocale>> locales = | ||
| GetLocales(locale_string, locale_storage); | ||
|
|
||
| EXPECT_EQ(locales.size(), 1UL); | ||
|
|
||
| EXPECT_STREQ(locales[0]->language_code, "C"); | ||
| EXPECT_STREQ(locales[0]->country_code, nullptr); | ||
| EXPECT_STREQ(locales[0]->script_code, nullptr); | ||
| EXPECT_STREQ(locales[0]->variant_code, nullptr); | ||
| } | ||
|
|
||
| TEST(FlutterGlfwTest, GetLocalesNull) { | ||
| const char* locale_string; | ||
| std::list<std::string> locale_storage; | ||
|
|
||
| std::vector<std::unique_ptr<FlutterLocale>> locales = | ||
| GetLocales(locale_string, locale_storage); | ||
|
|
||
| EXPECT_EQ(locales.size(), 1UL); | ||
|
|
||
| EXPECT_STREQ(locales[0]->language_code, "C"); | ||
| EXPECT_STREQ(locales[0]->country_code, nullptr); | ||
| EXPECT_STREQ(locales[0]->script_code, nullptr); | ||
| EXPECT_STREQ(locales[0]->variant_code, nullptr); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.