mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Send locale information in the Windows embedding (#20455)
Queries the system list of user-preferred languages, and sends it to the engine just after starting it up. Windows portion of https://github.com/flutter/flutter/issues/45152
This commit is contained in:
parent
32b1b70e27
commit
9060913dc9
@ -1359,6 +1359,9 @@ FILE: ../../../flutter/shell/platform/windows/public/flutter_windows.h
|
||||
FILE: ../../../flutter/shell/platform/windows/string_conversion.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/string_conversion.h
|
||||
FILE: ../../../flutter/shell/platform/windows/string_conversion_unittests.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/system_utils.h
|
||||
FILE: ../../../flutter/shell/platform/windows/system_utils_unittests.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/system_utils_win32.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/text_input_plugin.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/text_input_plugin.h
|
||||
FILE: ../../../flutter/shell/platform/windows/win32_dpi_utils.cc
|
||||
|
||||
@ -54,6 +54,8 @@ source_set("flutter_windows_source") {
|
||||
"keyboard_hook_handler.h",
|
||||
"string_conversion.cc",
|
||||
"string_conversion.h",
|
||||
"system_utils.h",
|
||||
"system_utils_win32.cc",
|
||||
"text_input_plugin.cc",
|
||||
"text_input_plugin.h",
|
||||
"win32_dpi_utils.cc",
|
||||
@ -118,6 +120,7 @@ executable("flutter_windows_unittests") {
|
||||
|
||||
sources = [
|
||||
"string_conversion_unittests.cc",
|
||||
"system_utils_unittests.cc",
|
||||
"testing/win32_flutter_window_test.cc",
|
||||
"testing/win32_flutter_window_test.h",
|
||||
"testing/win32_window_test.cc",
|
||||
|
||||
@ -6,9 +6,12 @@
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "flutter/shell/platform/common/cpp/path_utils.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_view.h"
|
||||
#include "flutter/shell/platform/windows/string_conversion.h"
|
||||
#include "flutter/shell/platform/windows/system_utils.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
@ -69,6 +72,21 @@ static FlutterDesktopMessage ConvertToDesktopMessage(
|
||||
return message;
|
||||
}
|
||||
|
||||
// Converts a LanguageInfo struct to a FlutterLocale struct. |info| must outlive
|
||||
// the returned value, since the returned FlutterLocale has pointers into it.
|
||||
FlutterLocale CovertToFlutterLocale(const LanguageInfo& info) {
|
||||
FlutterLocale locale = {};
|
||||
locale.struct_size = sizeof(FlutterLocale);
|
||||
locale.language_code = info.language.c_str();
|
||||
if (!info.region.empty()) {
|
||||
locale.country_code = info.region.c_str();
|
||||
}
|
||||
if (!info.script.empty()) {
|
||||
locale.script_code = info.script.c_str();
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FlutterWindowsEngine::FlutterWindowsEngine(const FlutterProjectBundle& project)
|
||||
@ -173,6 +191,8 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) {
|
||||
}
|
||||
|
||||
plugin_registrar_->messenger->engine = engine_;
|
||||
SendSystemSettings();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -213,4 +233,24 @@ void FlutterWindowsEngine::HandlePlatformMessage(
|
||||
message, [this] {}, [this] {});
|
||||
}
|
||||
|
||||
void FlutterWindowsEngine::SendSystemSettings() {
|
||||
std::vector<LanguageInfo> languages = GetPreferredLanguageInfo();
|
||||
std::vector<FlutterLocale> flutter_locales;
|
||||
flutter_locales.reserve(languages.size());
|
||||
for (const auto& info : languages) {
|
||||
flutter_locales.push_back(CovertToFlutterLocale(info));
|
||||
}
|
||||
// Convert the locale list to the locale pointer list that must be provided.
|
||||
std::vector<const FlutterLocale*> flutter_locale_list;
|
||||
flutter_locale_list.reserve(flutter_locales.size());
|
||||
std::transform(
|
||||
flutter_locales.begin(), flutter_locales.end(),
|
||||
std::back_inserter(flutter_locale_list),
|
||||
[](const auto& arg) -> const auto* { return &arg; });
|
||||
FlutterEngineUpdateLocales(engine_, flutter_locale_list.data(),
|
||||
flutter_locale_list.size());
|
||||
|
||||
// TODO: Send 'flutter/settings' channel settings here as well.
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@ -69,6 +69,12 @@ class FlutterWindowsEngine {
|
||||
void HandlePlatformMessage(const FlutterPlatformMessage*);
|
||||
|
||||
private:
|
||||
// Sends system settings (e.g., locale) to the engine.
|
||||
//
|
||||
// Should be called just after the engine is run, and after any relevant
|
||||
// system changes.
|
||||
void SendSystemSettings();
|
||||
|
||||
// The handle to the embedder.h engine instance.
|
||||
FLUTTER_API_SYMBOL(FlutterEngine) engine_ = nullptr;
|
||||
|
||||
|
||||
36
shell/platform/windows/system_utils.h
Normal file
36
shell/platform/windows/system_utils.h
Normal file
@ -0,0 +1,36 @@
|
||||
// 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.
|
||||
|
||||
// This file contains utilities for system-level information/settings.
|
||||
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_SYSTEM_UTILS_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_WINDOWS_SYSTEM_UTILS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace flutter {
|
||||
|
||||
// Components of a system language/locale.
|
||||
struct LanguageInfo {
|
||||
std::string language;
|
||||
std::string region;
|
||||
std::string script;
|
||||
};
|
||||
|
||||
// Returns the list of user-preferred languages, in preference order,
|
||||
// parsed into LanguageInfo structures.
|
||||
std::vector<LanguageInfo> GetPreferredLanguageInfo();
|
||||
|
||||
// Returns the list of user-preferred languages, in preference order.
|
||||
// The language names are as described at:
|
||||
// https://docs.microsoft.com/en-us/windows/win32/intl/language-names
|
||||
std::vector<std::wstring> GetPreferredLanguages();
|
||||
|
||||
// Parses a Windows language name into its components.
|
||||
LanguageInfo ParseLanguageName(std::wstring language_name);
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_SYSTEM_UTILS_H_
|
||||
75
shell/platform/windows/system_utils_unittests.cc
Normal file
75
shell/platform/windows/system_utils_unittests.cc
Normal file
@ -0,0 +1,75 @@
|
||||
// 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 <wchar.h>
|
||||
|
||||
#include "flutter/shell/platform/windows/system_utils.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace flutter {
|
||||
namespace testing {
|
||||
|
||||
TEST(SystemUtils, GetPreferredLanguageInfo) {
|
||||
std::vector<LanguageInfo> languages = GetPreferredLanguageInfo();
|
||||
// There should be at least one language.
|
||||
ASSERT_GE(languages.size(), 1);
|
||||
// The info should have a valid languge.
|
||||
EXPECT_GE(languages[0].language.size(), 2);
|
||||
}
|
||||
|
||||
TEST(SystemUtils, GetPreferredLanguages) {
|
||||
std::vector<std::wstring> languages = GetPreferredLanguages();
|
||||
// There should be at least one language.
|
||||
ASSERT_GE(languages.size(), 1);
|
||||
// The language should be non-empty.
|
||||
EXPECT_FALSE(languages[0].empty());
|
||||
// There should not be a trailing null from the parsing step.
|
||||
EXPECT_EQ(languages[0].size(), wcslen(languages[0].c_str()));
|
||||
}
|
||||
|
||||
TEST(SystemUtils, ParseLanguageNameGeneric) {
|
||||
LanguageInfo info = ParseLanguageName(L"en");
|
||||
EXPECT_EQ(info.language, "en");
|
||||
EXPECT_TRUE(info.region.empty());
|
||||
EXPECT_TRUE(info.script.empty());
|
||||
}
|
||||
|
||||
TEST(SystemUtils, ParseLanguageNameWithRegion) {
|
||||
LanguageInfo info = ParseLanguageName(L"hu-HU");
|
||||
EXPECT_EQ(info.language, "hu");
|
||||
EXPECT_EQ(info.region, "HU");
|
||||
EXPECT_TRUE(info.script.empty());
|
||||
}
|
||||
|
||||
TEST(SystemUtils, ParseLanguageNameWithScript) {
|
||||
LanguageInfo info = ParseLanguageName(L"us-Latn");
|
||||
EXPECT_EQ(info.language, "us");
|
||||
EXPECT_TRUE(info.region.empty());
|
||||
EXPECT_EQ(info.script, "Latn");
|
||||
}
|
||||
|
||||
TEST(SystemUtils, ParseLanguageNameWithRegionAndScript) {
|
||||
LanguageInfo info = ParseLanguageName(L"uz-Latn-UZ");
|
||||
EXPECT_EQ(info.language, "uz");
|
||||
EXPECT_EQ(info.region, "UZ");
|
||||
EXPECT_EQ(info.script, "Latn");
|
||||
}
|
||||
|
||||
TEST(SystemUtils, ParseLanguageNameWithSuplementalLanguage) {
|
||||
LanguageInfo info = ParseLanguageName(L"en-US-x-fabricam");
|
||||
EXPECT_EQ(info.language, "en");
|
||||
EXPECT_EQ(info.region, "US");
|
||||
EXPECT_TRUE(info.script.empty());
|
||||
}
|
||||
|
||||
// Ensure that ISO 639-2/T codes are handled.
|
||||
TEST(SystemUtils, ParseLanguageNameWithThreeCharacterLanguage) {
|
||||
LanguageInfo info = ParseLanguageName(L"ale-ZZ");
|
||||
EXPECT_EQ(info.language, "ale");
|
||||
EXPECT_EQ(info.region, "ZZ");
|
||||
EXPECT_TRUE(info.script.empty());
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
93
shell/platform/windows/system_utils_win32.cc
Normal file
93
shell/platform/windows/system_utils_win32.cc
Normal file
@ -0,0 +1,93 @@
|
||||
// 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/windows/system_utils.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "flutter/shell/platform/windows/string_conversion.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
std::vector<LanguageInfo> GetPreferredLanguageInfo() {
|
||||
std::vector<std::wstring> languages = GetPreferredLanguages();
|
||||
std::vector<LanguageInfo> language_info;
|
||||
language_info.reserve(languages.size());
|
||||
|
||||
for (auto language : languages) {
|
||||
language_info.push_back(ParseLanguageName(language));
|
||||
}
|
||||
return language_info;
|
||||
}
|
||||
|
||||
std::vector<std::wstring> GetPreferredLanguages() {
|
||||
std::vector<std::wstring> languages;
|
||||
DWORD flags = MUI_LANGUAGE_NAME | MUI_UI_FALLBACK;
|
||||
|
||||
// Get buffer length.
|
||||
ULONG count = 0;
|
||||
ULONG buffer_size = 0;
|
||||
if (!::GetThreadPreferredUILanguages(flags, &count, nullptr, &buffer_size)) {
|
||||
return languages;
|
||||
}
|
||||
|
||||
// Get the list of null-separated languages.
|
||||
std::wstring buffer(buffer_size, '\0');
|
||||
if (!::GetThreadPreferredUILanguages(flags, &count, buffer.data(),
|
||||
&buffer_size)) {
|
||||
return languages;
|
||||
}
|
||||
|
||||
// Extract the individual languages from the buffer.
|
||||
size_t start = 0;
|
||||
while (true) {
|
||||
// The buffer is terminated by an empty string (i.e., a double null).
|
||||
if (buffer[start] == L'\0') {
|
||||
break;
|
||||
}
|
||||
// Read the next null-terminated language.
|
||||
std::wstring language(buffer.c_str() + start);
|
||||
if (language.size() == 0) {
|
||||
break;
|
||||
}
|
||||
languages.push_back(language);
|
||||
// Skip past that language and its terminating null in the buffer.
|
||||
start += language.size() + 1;
|
||||
}
|
||||
return languages;
|
||||
}
|
||||
|
||||
LanguageInfo ParseLanguageName(std::wstring language_name) {
|
||||
LanguageInfo info;
|
||||
|
||||
// Split by '-', discarding any suplemental language info (-x-foo).
|
||||
std::vector<std::string> components;
|
||||
std::istringstream stream(Utf8FromUtf16(language_name));
|
||||
std::string component;
|
||||
while (getline(stream, component, '-')) {
|
||||
if (component == "x") {
|
||||
break;
|
||||
}
|
||||
components.push_back(component);
|
||||
}
|
||||
|
||||
// Determine which components are which.
|
||||
info.language = components[0];
|
||||
if (components.size() == 3) {
|
||||
info.script = components[1];
|
||||
info.region = components[2];
|
||||
} else if (components.size() == 2) {
|
||||
// A script code will always be four characters long.
|
||||
if (components[1].size() == 4) {
|
||||
info.script = components[1];
|
||||
} else {
|
||||
info.region = components[1];
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
Loading…
x
Reference in New Issue
Block a user