mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] Add Intl.Locale to parse browser languages. (#172964)
Closes https://github.com/flutter/flutter/issues/130174 ### Description - Adds `DomLocale` extension type for [`Intl.Locale`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale) - Replaces manual browser language parsing with `DomLocale` usage - Adds tests to cover new functionality ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
fe07188bf3
commit
a128ccbe94
@ -2444,6 +2444,50 @@ extension type DomSegments._(JSObject _) implements JSObject {
|
||||
}
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale
|
||||
@JS('Intl.Locale')
|
||||
extension type DomLocale._(JSObject _) implements JSObject {
|
||||
external DomLocale(String tag, [DomLocaleOptions? options]);
|
||||
|
||||
external String get language;
|
||||
external String? get script;
|
||||
external String? get region;
|
||||
external String? get calendar;
|
||||
external String? get caseFirst;
|
||||
external String? get collation;
|
||||
external String? get hourCycle;
|
||||
external String? get numberingSystem;
|
||||
external bool? get numeric;
|
||||
|
||||
@JS('toString')
|
||||
external String toJSString();
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/Locale#options
|
||||
extension type DomLocaleOptions._(JSObject _) implements JSObject {
|
||||
external DomLocaleOptions({
|
||||
String? language,
|
||||
String? script,
|
||||
String? region,
|
||||
String? calendar,
|
||||
String? caseFirst,
|
||||
String? collation,
|
||||
String? hourCycle,
|
||||
String? numberingSystem,
|
||||
bool? numeric,
|
||||
});
|
||||
|
||||
external String? get language;
|
||||
external String? get script;
|
||||
external String? get region;
|
||||
external String? get calendar;
|
||||
external String? get caseFirst;
|
||||
external String? get collation;
|
||||
external String? get hourCycle;
|
||||
external String? get numberingSystem;
|
||||
external bool? get numeric;
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
|
||||
@JS('Iterator')
|
||||
extension type DomIterator._(JSObject _) implements JSObject {
|
||||
|
||||
@ -901,9 +901,22 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
configuration = configuration.copyWith(locales: parseBrowserLanguages());
|
||||
}
|
||||
|
||||
/// Overrides the browser languages list.
|
||||
///
|
||||
/// If [value] is null, resets the browser languages back to the real value.
|
||||
///
|
||||
/// This is intended for tests only.
|
||||
@visibleForTesting
|
||||
static void debugOverrideBrowserLanguages(List<String>? value) {
|
||||
_browserLanguagesOverride = value;
|
||||
}
|
||||
|
||||
static List<String>? _browserLanguagesOverride;
|
||||
|
||||
@visibleForTesting
|
||||
static List<ui.Locale> parseBrowserLanguages() {
|
||||
// TODO(yjbanov): find a solution for IE
|
||||
final List<String>? languages = domWindow.navigator.languages;
|
||||
final List<String>? languages = _browserLanguagesOverride ?? domWindow.navigator.languages;
|
||||
if (languages == null || languages.isEmpty) {
|
||||
// To make it easier for the app code, let's not leave the locales list
|
||||
// empty. This way there's fewer corner cases for apps to handle.
|
||||
@ -912,12 +925,14 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
|
||||
final List<ui.Locale> locales = <ui.Locale>[];
|
||||
for (final String language in languages) {
|
||||
final List<String> parts = language.split('-');
|
||||
if (parts.length > 1) {
|
||||
locales.add(ui.Locale(parts.first, parts.last));
|
||||
} else {
|
||||
locales.add(ui.Locale(language));
|
||||
}
|
||||
final DomLocale domLocale = DomLocale(language);
|
||||
locales.add(
|
||||
ui.Locale.fromSubtags(
|
||||
languageCode: domLocale.language,
|
||||
scriptCode: domLocale.script,
|
||||
countryCode: domLocale.region,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
assert(locales.isNotEmpty);
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
void main() {
|
||||
@ -60,4 +61,66 @@ void testMain() {
|
||||
isNot(const Locale.fromSubtags(languageCode: 'en', scriptCode: 'Latn').hashCode),
|
||||
);
|
||||
});
|
||||
|
||||
test('DomLocale', () {
|
||||
final locale1 = DomLocale('uk-UA');
|
||||
|
||||
expect(locale1.language, 'uk');
|
||||
expect(locale1.script, isNull);
|
||||
expect(locale1.region, 'UA');
|
||||
expect(locale1.calendar, isNull);
|
||||
expect(locale1.caseFirst, isNull);
|
||||
expect(locale1.collation, isNull);
|
||||
expect(locale1.hourCycle, isNull);
|
||||
expect(locale1.numberingSystem, isNull);
|
||||
expect(locale1.numeric, false);
|
||||
|
||||
final locale2 = DomLocale('en-Latn-US');
|
||||
|
||||
expect(locale2.language, 'en');
|
||||
expect(locale2.script, 'Latn');
|
||||
expect(locale2.region, 'US');
|
||||
expect(locale2.calendar, isNull);
|
||||
expect(locale2.caseFirst, isNull);
|
||||
expect(locale2.collation, isNull);
|
||||
expect(locale2.hourCycle, isNull);
|
||||
expect(locale2.numberingSystem, isNull);
|
||||
expect(locale2.numeric, false);
|
||||
|
||||
final locale3 = DomLocale('de-Latn-DE-u-ca-gregory-kf-upper-co-dict-hc-h24-nu-latn-kn-true');
|
||||
|
||||
expect(locale3.language, 'de');
|
||||
expect(locale3.script, 'Latn');
|
||||
expect(locale3.region, 'DE');
|
||||
expect(locale3.calendar, 'gregory');
|
||||
expect(locale3.caseFirst, 'upper');
|
||||
expect(locale3.collation, 'dict');
|
||||
expect(locale3.hourCycle, 'h24');
|
||||
expect(locale3.numberingSystem, 'latn');
|
||||
expect(locale3.numeric, isTrue);
|
||||
|
||||
final locale4 = DomLocale(
|
||||
'th',
|
||||
DomLocaleOptions(
|
||||
script: 'Thai',
|
||||
region: 'TH',
|
||||
calendar: 'buddhist',
|
||||
caseFirst: 'lower',
|
||||
collation: 'dict',
|
||||
hourCycle: 'h12',
|
||||
numberingSystem: 'thai',
|
||||
numeric: true,
|
||||
),
|
||||
);
|
||||
|
||||
expect(locale4.language, 'th');
|
||||
expect(locale4.script, 'Thai');
|
||||
expect(locale4.region, 'TH');
|
||||
expect(locale4.calendar, 'buddhist');
|
||||
expect(locale4.caseFirst, 'lower');
|
||||
expect(locale4.collation, 'dict');
|
||||
expect(locale4.hourCycle, 'h12');
|
||||
expect(locale4.numberingSystem, 'thai');
|
||||
expect(locale4.numeric, true);
|
||||
});
|
||||
}
|
||||
|
||||
@ -430,6 +430,34 @@ void testMain() {
|
||||
});
|
||||
});
|
||||
|
||||
group('parseBrowserLanguages', () {
|
||||
test('returns the default locale when no browser languages are present', () {
|
||||
EnginePlatformDispatcher.debugOverrideBrowserLanguages([]);
|
||||
addTearDown(() => EnginePlatformDispatcher.debugOverrideBrowserLanguages(null));
|
||||
|
||||
expect(EnginePlatformDispatcher.parseBrowserLanguages(), const [ui.Locale('en', 'US')]);
|
||||
});
|
||||
|
||||
test('returns locales list parsed from browser languages', () {
|
||||
EnginePlatformDispatcher.debugOverrideBrowserLanguages([
|
||||
'uk-UA',
|
||||
'en',
|
||||
'ar-Arab-SA',
|
||||
'zh-Hant-HK',
|
||||
'de-DE',
|
||||
]);
|
||||
addTearDown(() => EnginePlatformDispatcher.debugOverrideBrowserLanguages(null));
|
||||
|
||||
expect(EnginePlatformDispatcher.parseBrowserLanguages(), const [
|
||||
ui.Locale('uk', 'UA'),
|
||||
ui.Locale('en'),
|
||||
ui.Locale.fromSubtags(languageCode: 'ar', scriptCode: 'Arab', countryCode: 'SA'),
|
||||
ui.Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'),
|
||||
ui.Locale('de', 'DE'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('AT Focus Handler Integration', () {
|
||||
test('navigation focus handler is registered during initialization', () {
|
||||
final DomElement navButton = createDomHTMLButtonElement();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user