Skip to content

Commit 9060913

Browse files
Send locale information in the Windows embedding (flutter#20455)
Queries the system list of user-preferred languages, and sends it to the engine just after starting it up. Windows portion of flutter#45152
1 parent 32b1b70 commit 9060913

File tree

7 files changed

+256
-0
lines changed

7 files changed

+256
-0
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,9 @@ FILE: ../../../flutter/shell/platform/windows/public/flutter_windows.h
13591359
FILE: ../../../flutter/shell/platform/windows/string_conversion.cc
13601360
FILE: ../../../flutter/shell/platform/windows/string_conversion.h
13611361
FILE: ../../../flutter/shell/platform/windows/string_conversion_unittests.cc
1362+
FILE: ../../../flutter/shell/platform/windows/system_utils.h
1363+
FILE: ../../../flutter/shell/platform/windows/system_utils_unittests.cc
1364+
FILE: ../../../flutter/shell/platform/windows/system_utils_win32.cc
13621365
FILE: ../../../flutter/shell/platform/windows/text_input_plugin.cc
13631366
FILE: ../../../flutter/shell/platform/windows/text_input_plugin.h
13641367
FILE: ../../../flutter/shell/platform/windows/win32_dpi_utils.cc

shell/platform/windows/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ source_set("flutter_windows_source") {
5454
"keyboard_hook_handler.h",
5555
"string_conversion.cc",
5656
"string_conversion.h",
57+
"system_utils.h",
58+
"system_utils_win32.cc",
5759
"text_input_plugin.cc",
5860
"text_input_plugin.h",
5961
"win32_dpi_utils.cc",
@@ -118,6 +120,7 @@ executable("flutter_windows_unittests") {
118120

119121
sources = [
120122
"string_conversion_unittests.cc",
123+
"system_utils_unittests.cc",
121124
"testing/win32_flutter_window_test.cc",
122125
"testing/win32_flutter_window_test.h",
123126
"testing/win32_window_test.cc",

shell/platform/windows/flutter_windows_engine.cc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66

77
#include <filesystem>
88
#include <iostream>
9+
#include <sstream>
910

1011
#include "flutter/shell/platform/common/cpp/path_utils.h"
1112
#include "flutter/shell/platform/windows/flutter_windows_view.h"
13+
#include "flutter/shell/platform/windows/string_conversion.h"
14+
#include "flutter/shell/platform/windows/system_utils.h"
1215

1316
namespace flutter {
1417

@@ -69,6 +72,21 @@ static FlutterDesktopMessage ConvertToDesktopMessage(
6972
return message;
7073
}
7174

75+
// Converts a LanguageInfo struct to a FlutterLocale struct. |info| must outlive
76+
// the returned value, since the returned FlutterLocale has pointers into it.
77+
FlutterLocale CovertToFlutterLocale(const LanguageInfo& info) {
78+
FlutterLocale locale = {};
79+
locale.struct_size = sizeof(FlutterLocale);
80+
locale.language_code = info.language.c_str();
81+
if (!info.region.empty()) {
82+
locale.country_code = info.region.c_str();
83+
}
84+
if (!info.script.empty()) {
85+
locale.script_code = info.script.c_str();
86+
}
87+
return locale;
88+
}
89+
7290
} // namespace
7391

7492
FlutterWindowsEngine::FlutterWindowsEngine(const FlutterProjectBundle& project)
@@ -173,6 +191,8 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) {
173191
}
174192

175193
plugin_registrar_->messenger->engine = engine_;
194+
SendSystemSettings();
195+
176196
return true;
177197
}
178198

@@ -213,4 +233,24 @@ void FlutterWindowsEngine::HandlePlatformMessage(
213233
message, [this] {}, [this] {});
214234
}
215235

236+
void FlutterWindowsEngine::SendSystemSettings() {
237+
std::vector<LanguageInfo> languages = GetPreferredLanguageInfo();
238+
std::vector<FlutterLocale> flutter_locales;
239+
flutter_locales.reserve(languages.size());
240+
for (const auto& info : languages) {
241+
flutter_locales.push_back(CovertToFlutterLocale(info));
242+
}
243+
// Convert the locale list to the locale pointer list that must be provided.
244+
std::vector<const FlutterLocale*> flutter_locale_list;
245+
flutter_locale_list.reserve(flutter_locales.size());
246+
std::transform(
247+
flutter_locales.begin(), flutter_locales.end(),
248+
std::back_inserter(flutter_locale_list),
249+
[](const auto& arg) -> const auto* { return &arg; });
250+
FlutterEngineUpdateLocales(engine_, flutter_locale_list.data(),
251+
flutter_locale_list.size());
252+
253+
// TODO: Send 'flutter/settings' channel settings here as well.
254+
}
255+
216256
} // namespace flutter

shell/platform/windows/flutter_windows_engine.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ class FlutterWindowsEngine {
6969
void HandlePlatformMessage(const FlutterPlatformMessage*);
7070

7171
private:
72+
// Sends system settings (e.g., locale) to the engine.
73+
//
74+
// Should be called just after the engine is run, and after any relevant
75+
// system changes.
76+
void SendSystemSettings();
77+
7278
// The handle to the embedder.h engine instance.
7379
FLUTTER_API_SYMBOL(FlutterEngine) engine_ = nullptr;
7480

shell/platform/windows/system_utils.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// This file contains utilities for system-level information/settings.
6+
7+
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_SYSTEM_UTILS_H_
8+
#define FLUTTER_SHELL_PLATFORM_WINDOWS_SYSTEM_UTILS_H_
9+
10+
#include <string>
11+
#include <vector>
12+
13+
namespace flutter {
14+
15+
// Components of a system language/locale.
16+
struct LanguageInfo {
17+
std::string language;
18+
std::string region;
19+
std::string script;
20+
};
21+
22+
// Returns the list of user-preferred languages, in preference order,
23+
// parsed into LanguageInfo structures.
24+
std::vector<LanguageInfo> GetPreferredLanguageInfo();
25+
26+
// Returns the list of user-preferred languages, in preference order.
27+
// The language names are as described at:
28+
// https://docs.microsoft.com/en-us/windows/win32/intl/language-names
29+
std::vector<std::wstring> GetPreferredLanguages();
30+
31+
// Parses a Windows language name into its components.
32+
LanguageInfo ParseLanguageName(std::wstring language_name);
33+
34+
} // namespace flutter
35+
36+
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_SYSTEM_UTILS_H_
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include <wchar.h>
6+
7+
#include "flutter/shell/platform/windows/system_utils.h"
8+
#include "gtest/gtest.h"
9+
10+
namespace flutter {
11+
namespace testing {
12+
13+
TEST(SystemUtils, GetPreferredLanguageInfo) {
14+
std::vector<LanguageInfo> languages = GetPreferredLanguageInfo();
15+
// There should be at least one language.
16+
ASSERT_GE(languages.size(), 1);
17+
// The info should have a valid languge.
18+
EXPECT_GE(languages[0].language.size(), 2);
19+
}
20+
21+
TEST(SystemUtils, GetPreferredLanguages) {
22+
std::vector<std::wstring> languages = GetPreferredLanguages();
23+
// There should be at least one language.
24+
ASSERT_GE(languages.size(), 1);
25+
// The language should be non-empty.
26+
EXPECT_FALSE(languages[0].empty());
27+
// There should not be a trailing null from the parsing step.
28+
EXPECT_EQ(languages[0].size(), wcslen(languages[0].c_str()));
29+
}
30+
31+
TEST(SystemUtils, ParseLanguageNameGeneric) {
32+
LanguageInfo info = ParseLanguageName(L"en");
33+
EXPECT_EQ(info.language, "en");
34+
EXPECT_TRUE(info.region.empty());
35+
EXPECT_TRUE(info.script.empty());
36+
}
37+
38+
TEST(SystemUtils, ParseLanguageNameWithRegion) {
39+
LanguageInfo info = ParseLanguageName(L"hu-HU");
40+
EXPECT_EQ(info.language, "hu");
41+
EXPECT_EQ(info.region, "HU");
42+
EXPECT_TRUE(info.script.empty());
43+
}
44+
45+
TEST(SystemUtils, ParseLanguageNameWithScript) {
46+
LanguageInfo info = ParseLanguageName(L"us-Latn");
47+
EXPECT_EQ(info.language, "us");
48+
EXPECT_TRUE(info.region.empty());
49+
EXPECT_EQ(info.script, "Latn");
50+
}
51+
52+
TEST(SystemUtils, ParseLanguageNameWithRegionAndScript) {
53+
LanguageInfo info = ParseLanguageName(L"uz-Latn-UZ");
54+
EXPECT_EQ(info.language, "uz");
55+
EXPECT_EQ(info.region, "UZ");
56+
EXPECT_EQ(info.script, "Latn");
57+
}
58+
59+
TEST(SystemUtils, ParseLanguageNameWithSuplementalLanguage) {
60+
LanguageInfo info = ParseLanguageName(L"en-US-x-fabricam");
61+
EXPECT_EQ(info.language, "en");
62+
EXPECT_EQ(info.region, "US");
63+
EXPECT_TRUE(info.script.empty());
64+
}
65+
66+
// Ensure that ISO 639-2/T codes are handled.
67+
TEST(SystemUtils, ParseLanguageNameWithThreeCharacterLanguage) {
68+
LanguageInfo info = ParseLanguageName(L"ale-ZZ");
69+
EXPECT_EQ(info.language, "ale");
70+
EXPECT_EQ(info.region, "ZZ");
71+
EXPECT_TRUE(info.script.empty());
72+
}
73+
74+
} // namespace testing
75+
} // namespace flutter
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/shell/platform/windows/system_utils.h"
6+
7+
#include <Windows.h>
8+
9+
#include <sstream>
10+
11+
#include "flutter/shell/platform/windows/string_conversion.h"
12+
13+
namespace flutter {
14+
15+
std::vector<LanguageInfo> GetPreferredLanguageInfo() {
16+
std::vector<std::wstring> languages = GetPreferredLanguages();
17+
std::vector<LanguageInfo> language_info;
18+
language_info.reserve(languages.size());
19+
20+
for (auto language : languages) {
21+
language_info.push_back(ParseLanguageName(language));
22+
}
23+
return language_info;
24+
}
25+
26+
std::vector<std::wstring> GetPreferredLanguages() {
27+
std::vector<std::wstring> languages;
28+
DWORD flags = MUI_LANGUAGE_NAME | MUI_UI_FALLBACK;
29+
30+
// Get buffer length.
31+
ULONG count = 0;
32+
ULONG buffer_size = 0;
33+
if (!::GetThreadPreferredUILanguages(flags, &count, nullptr, &buffer_size)) {
34+
return languages;
35+
}
36+
37+
// Get the list of null-separated languages.
38+
std::wstring buffer(buffer_size, '\0');
39+
if (!::GetThreadPreferredUILanguages(flags, &count, buffer.data(),
40+
&buffer_size)) {
41+
return languages;
42+
}
43+
44+
// Extract the individual languages from the buffer.
45+
size_t start = 0;
46+
while (true) {
47+
// The buffer is terminated by an empty string (i.e., a double null).
48+
if (buffer[start] == L'\0') {
49+
break;
50+
}
51+
// Read the next null-terminated language.
52+
std::wstring language(buffer.c_str() + start);
53+
if (language.size() == 0) {
54+
break;
55+
}
56+
languages.push_back(language);
57+
// Skip past that language and its terminating null in the buffer.
58+
start += language.size() + 1;
59+
}
60+
return languages;
61+
}
62+
63+
LanguageInfo ParseLanguageName(std::wstring language_name) {
64+
LanguageInfo info;
65+
66+
// Split by '-', discarding any suplemental language info (-x-foo).
67+
std::vector<std::string> components;
68+
std::istringstream stream(Utf8FromUtf16(language_name));
69+
std::string component;
70+
while (getline(stream, component, '-')) {
71+
if (component == "x") {
72+
break;
73+
}
74+
components.push_back(component);
75+
}
76+
77+
// Determine which components are which.
78+
info.language = components[0];
79+
if (components.size() == 3) {
80+
info.script = components[1];
81+
info.region = components[2];
82+
} else if (components.size() == 2) {
83+
// A script code will always be four characters long.
84+
if (components[1].size() == 4) {
85+
info.script = components[1];
86+
} else {
87+
info.region = components[1];
88+
}
89+
}
90+
return info;
91+
}
92+
93+
} // namespace flutter

0 commit comments

Comments
 (0)