Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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
12 changes: 9 additions & 3 deletions lib/ui/hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,20 @@ void _updateWindowMetrics(double devicePixelRatio,

typedef _LocaleClosure = String Function();

String _localeClosure() => window._locale.toString();
String _localeClosure() => window.locale.toString();

@pragma('vm:entry-point')
_LocaleClosure _getLocaleClosure() => _localeClosure;

@pragma('vm:entry-point')
void _updateLocale(String languageCode, String countryCode, String scriptCode, String variantCode) {
window._locale = new Locale(languageCode, countryCode);
void _updateLocales(List<String> locales) {
const int stringsPerLocale = 4;
final int numLocales = locales.length ~/ stringsPerLocale;
window._locales = new List<Locale>(numLocales);
for (int localeIndex = 0; localeIndex < numLocales; localeIndex++) {
window._locales[localeIndex] = new Locale(locales[localeIndex * stringsPerLocale],
locales[localeIndex * stringsPerLocale + 1]);
}
_invoke(window.onLocaleChanged, window._onLocaleChangedZone);
}

Expand Down
29 changes: 26 additions & 3 deletions lib/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ class Locale {
/// the region subtag should be uppercase.
const Locale(this._languageCode, [ this._countryCode ]) : assert(_languageCode != null);

/// Empty locale constant. This is an invalid locale.
static const Locale none = const Locale('', '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not null?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or just assert that it's never empty -- can we ever have no locale at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be a large string of calls that do not expect the returned locale to ever be null. I can look into changing it to do null checking though, since that is probably a more robust design in the long run.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mostly applies to when the locale is accessed before the platform has had a chance to pass it over on startup. The empty locale would just result in the resolution to use the default (first) locale.


/// The primary language subtag for the locale.
///
/// This must not be null.
Expand Down Expand Up @@ -426,19 +429,39 @@ class Window {
_onMetricsChangedZone = Zone.current;
}

/// The system-reported locale.
/// The system-reported default locale of the device.
///
/// This establishes the language and formatting conventions that application
/// should, if possible, use to render their user interface.
///
/// This is the first locale selected by the user and is the user's
/// primary locale (the locale the device UI is displayed in)
///
/// This is equivalent to `locales.first` and will provide an empty non-null locale
/// if the [locales] list has not been set or is empty.
Locale get locale {
if (_locales != null && _locales.isNotEmpty) {
return _locales.first;
}
return Locale.none;
}

/// The full system-reported supported locales of the device.
///
/// This establishes the language and formatting conventions that application
/// should, if possible, use to render their user interface.
///
/// The list is ordered in order of priority, with lower-indexed locales being
/// preferred over higher-indexed ones. The first element is the primary [locale].
///
/// The [onLocaleChanged] callback is called whenever this value changes.
///
/// See also:
///
/// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
/// observe when this value changes.
Locale get locale => _locale;
Locale _locale;
List<Locale> get locales => _locales;
List<Locale> _locales;

/// A callback that is invoked whenever [locale] changes value.
///
Expand Down
13 changes: 3 additions & 10 deletions lib/ui/window/window.cc
Original file line number Diff line number Diff line change
Expand Up @@ -162,21 +162,14 @@ void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) {
});
}

void Window::UpdateLocale(const std::string& language_code,
const std::string& country_code,
const std::string& script_code,
const std::string& variant_code) {
void Window::UpdateLocales(const std::vector<std::string>& locales) {
std::shared_ptr<tonic::DartState> dart_state = library_.dart_state().lock();
if (!dart_state)
return;
tonic::DartState::Scope scope(dart_state);

DartInvokeField(library_.value(), "_updateLocale",
DartInvokeField(library_.value(), "_updateLocales",
{
StdStringToDart(language_code),
StdStringToDart(country_code),
StdStringToDart(script_code),
StdStringToDart(variant_code),
tonic::ToDart<std::vector<std::string>>(locales),
});
}

Expand Down
7 changes: 3 additions & 4 deletions lib/ui/window/window.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#ifndef FLUTTER_LIB_UI_WINDOW_WINDOW_H_
#define FLUTTER_LIB_UI_WINDOW_WINDOW_H_

#include <string>
#include <unordered_map>
#include <vector>

#include "flutter/fml/time/time_point.h"
#include "flutter/lib/ui/semantics/semantics_update.h"
Expand Down Expand Up @@ -59,10 +61,7 @@ class Window final {

void DidCreateIsolate();
void UpdateWindowMetrics(const ViewportMetrics& metrics);
void UpdateLocale(const std::string& language_code,
const std::string& country_code,
const std::string& script_code,
const std::string& variant_code);
void UpdateLocales(const std::vector<std::string>& locales);
void UpdateUserSettingsData(const std::string& data);
void UpdateSemanticsEnabled(bool enabled);
void UpdateAccessibilityFeatures(int32_t flags);
Expand Down
20 changes: 6 additions & 14 deletions runtime/runtime_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,7 @@ std::unique_ptr<RuntimeController> RuntimeController::Clone() const {

bool RuntimeController::FlushRuntimeStateToIsolate() {
return SetViewportMetrics(window_data_.viewport_metrics) &&
SetLocale(window_data_.language_code, window_data_.country_code,
window_data_.script_code, window_data_.variant_code) &&
SetLocales(window_data_.locale_data) &&
SetSemanticsEnabled(window_data_.semantics_enabled) &&
SetAccessibilityFeatures(window_data_.accessibility_feature_flags_);
}
Expand All @@ -140,22 +139,15 @@ bool RuntimeController::SetViewportMetrics(const ViewportMetrics& metrics) {
return false;
}

bool RuntimeController::SetLocale(const std::string& language_code,
const std::string& country_code,
const std::string& script_code,
const std::string& variant_code) {
window_data_.language_code = language_code;
window_data_.country_code = country_code;
window_data_.script_code = script_code;
window_data_.variant_code = variant_code;
bool RuntimeController::SetLocales(
const std::vector<std::string>& locale_data) {
window_data_.locale_data = locale_data;

if (auto window = GetWindowIfAvailable()) {
window->UpdateLocale(window_data_.language_code, window_data_.country_code,
window_data_.script_code, window_data_.variant_code);
window->UpdateLocales(locale_data);
return true;
}

return false;
return true;
}

bool RuntimeController::SetUserSettingsData(const std::string& data) {
Expand Down
25 changes: 21 additions & 4 deletions runtime/runtime_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define FLUTTER_RUNTIME_RUNTIME_CONTROLLER_H_

#include <memory>
#include <vector>

#include "flutter/common/task_runners.h"
#include "flutter/flow/layers/layer_tree.h"
Expand All @@ -15,6 +16,8 @@
#include "flutter/lib/ui/window/pointer_data_packet.h"
#include "flutter/lib/ui/window/window.h"
#include "flutter/runtime/dart_vm.h"
#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"

namespace blink {
class Scene;
Expand All @@ -40,10 +43,7 @@ class RuntimeController final : public WindowClient {

bool SetViewportMetrics(const ViewportMetrics& metrics);

bool SetLocale(const std::string& language_code,
const std::string& country_code,
const std::string& script_code,
const std::string& variant_code);
bool SetLocales(const std::vector<std::string>& locale_data);

bool SetUserSettingsData(const std::string& data);

Expand Down Expand Up @@ -78,12 +78,29 @@ class RuntimeController final : public WindowClient {
std::pair<bool, uint32_t> GetRootIsolateReturnCode();

private:
struct Locale {
Locale(std::string language_code_,
std::string country_code_,
std::string script_code_,
std::string variant_code_)
: language_code(language_code_),
country_code(country_code_),
script_code(script_code_),
variant_code(variant_code_) {}

std::string language_code;
std::string country_code;
std::string script_code;
std::string variant_code;
};

struct WindowData {
ViewportMetrics viewport_metrics;
std::string language_code;
std::string country_code;
std::string script_code;
std::string variant_code;
std::vector<std::string> locale_data;
std::string user_settings_data = "{}";
bool semantics_enabled = false;
bool assistive_technology_enabled = false;
Expand Down
24 changes: 15 additions & 9 deletions shell/common/engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "flutter/common/settings.h"
#include "flutter/fml/eintr_wrapper.h"
Expand Down Expand Up @@ -325,17 +326,22 @@ bool Engine::HandleLocalizationPlatformMessage(
if (args == root.MemberEnd() || !args->value.IsArray())
return false;

const auto& language = args->value[0];
const auto& country = args->value[1];
const auto& script = args->value[2];
const auto& variant = args->value[3];

if (!language.IsString() || !country.IsString())
const size_t strings_per_locale = 4;
if (args->value.Size() % strings_per_locale != 0)
return false;
std::vector<std::string> locale_data;
for (size_t locale_index = 0; locale_index < args->value.Size();
locale_index += strings_per_locale) {
if (!args->value[locale_index].IsString() ||
!args->value[locale_index + 1].IsString())
return false;
locale_data.push_back(args->value[locale_index].GetString());
locale_data.push_back(args->value[locale_index + 1].GetString());
locale_data.push_back(args->value[locale_index + 2].GetString());
locale_data.push_back(args->value[locale_index + 3].GetString());
}

return runtime_controller_->SetLocale(language.GetString(),
country.GetString(), script.GetString(),
variant.GetString());
return runtime_controller_->SetLocales(locale_data);
}

void Engine::HandleSettingsPlatformMessage(blink::PlatformMessage* message) {
Expand Down
37 changes: 34 additions & 3 deletions shell/platform/android/io/flutter/view/FlutterView.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.json.JSONException;
import org.json.JSONObject;

import java.lang.reflect.Method;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
Expand Down Expand Up @@ -170,7 +171,8 @@ public void surfaceDestroyed(SurfaceHolder holder) {
mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
mTextInputPlugin = new TextInputPlugin(this);

setLocale(getResources().getConfiguration().locale);

setLocales(getResources().getConfiguration());
setUserSettings();
}

Expand Down Expand Up @@ -316,14 +318,43 @@ private void setUserSettings() {
mFlutterSettingsChannel.send(message);
}

private void setLocale(Locale locale) {
private void setLocales(Configuration config) {
if (Build.VERSION.SDK_INT >= 24) {
try {
// Passes the full list of locales for android API >= 24 with reflection.
Object localeList = config.getClass().getDeclaredMethod("getLocales").invoke(config);
Method localeListGet = localeList.getClass().getDeclaredMethod("get", int.class);
Method localeListSize = localeList.getClass().getDeclaredMethod("size");
int localeCount = (int)localeListSize.invoke(localeList);
List<String> data = new ArrayList<String>();
for (int index = 0; index < localeCount; ++index) {
Locale locale = (Locale)localeListGet.invoke(localeList, index);
data.add(locale.getLanguage());
data.add(locale.getCountry());
data.add(locale.getScript());
data.add(locale.getVariant());
}
mFlutterLocalizationChannel.invokeMethod("setLocale", data);
return;
} catch (Exception exception) {
// Any exception is a failure. Resort to fallback of sending only one locale.
}
}
// Fallback single locale passing for android API < 24. Should work always.
Locale locale = config.locale;
List<String> data = new ArrayList<String>();
data.add(locale.getLanguage());
data.add(locale.getCountry());
data.add(locale.getScript());
data.add(locale.getVariant());
mFlutterLocalizationChannel.invokeMethod("setLocale", Arrays.asList(locale.getLanguage(), locale.getCountry(), locale.getScript(), locale.getVariant()));

}

@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setLocale(newConfig.locale);
setLocales(newConfig);
setUserSettings();
}

Expand Down
33 changes: 20 additions & 13 deletions shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -905,19 +905,26 @@ - (void)onMemoryWarning:(NSNotification*)notification {
#pragma mark - Locale updates

- (void)onLocaleUpdated:(NSNotification*)notification {
NSLocale* currentLocale = [NSLocale currentLocale];
NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode];
NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
NSString* scriptCode = [currentLocale objectForKey:NSLocaleScriptCode];
NSString* variantCode = [currentLocale objectForKey:NSLocaleVariantCode];
if (languageCode && countryCode)
// We pass empty strings for undefined scripts and variants to ensure the JSON encoder/decoder
// functions properly.
[_localizationChannel.get() invokeMethod:@"setLocale"
arguments:@[
languageCode, countryCode, scriptCode ? scriptCode : @"",
variantCode ? variantCode : @""
]];
NSArray<NSString*>* preferredLocales = [NSLocale preferredLanguages];
NSMutableArray<NSString*>* data = [NSMutableArray new];
for (NSString* localeID in preferredLocales) {
NSLocale* currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:localeID];
NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode];
NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
NSString* scriptCode = [currentLocale objectForKey:NSLocaleScriptCode];
NSString* variantCode = [currentLocale objectForKey:NSLocaleVariantCode];
if (!languageCode || !countryCode) {
continue;
}
[data addObject:languageCode];
[data addObject:countryCode];
[data addObject:(scriptCode ? scriptCode : @"")];
[data addObject:(variantCode ? variantCode : @"")];
}
if (data.count == 0) {
return;
}
[_localizationChannel.get() invokeMethod:@"setLocale" arguments:data];
}

#pragma mark - Set user settings
Expand Down
2 changes: 1 addition & 1 deletion testing/dart/window_hooks_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ void main() {
};
});

_updateLocale('en', 'US', '', '');
_updateLocales(<String>['en', 'US', '', '']);
expect(runZone, isNotNull);
expect(runZone, same(innerZone));
expect(locale, equals(const Locale('en', 'US')));
Expand Down