Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Send locale information in the Windows embedding #20455

Merged
merged 3 commits into from
Aug 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,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
Expand Down
3 changes: 3 additions & 0 deletions shell/platform/windows/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
40 changes: 40 additions & 0 deletions shell/platform/windows/flutter_windows_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -173,6 +191,8 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) {
}

plugin_registrar_->messenger->engine = engine_;
SendSystemSettings();

return true;
}

Expand Down Expand Up @@ -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
6 changes: 6 additions & 0 deletions shell/platform/windows/flutter_windows_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,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;

Expand Down
36 changes: 36 additions & 0 deletions shell/platform/windows/system_utils.h
Original file line number Diff line number Diff line change
@@ -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 changes: 75 additions & 0 deletions shell/platform/windows/system_utils_unittests.cc
Original file line number Diff line number Diff line change
@@ -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 changes: 93 additions & 0 deletions shell/platform/windows/system_utils_win32.cc
Original file line number Diff line number Diff line change
@@ -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