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:
stuartmorgan 2020-08-17 05:46:39 -07:00 committed by GitHub
parent 32b1b70e27
commit 9060913dc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 256 additions and 0 deletions

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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;

View 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_

View 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

View 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