@@ -23,7 +23,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
2323 /// The current platform configuration.
2424 @override
2525 ui.PlatformConfiguration get configuration => _configuration;
26- ui.PlatformConfiguration _configuration = const ui.PlatformConfiguration ();
26+ ui.PlatformConfiguration _configuration = ui.PlatformConfiguration (locales : parseBrowserLanguages () );
2727
2828 /// Receives all events related to platform configuration changes.
2929 @override
@@ -310,9 +310,9 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
310310
311311 @override
312312 void sendPlatformMessage (
313- String name,
314- ByteData data,
315- ui.PlatformMessageResponseCallback callback,
313+ String /*!*/ name,
314+ ByteData /*?*/ data,
315+ ui.PlatformMessageResponseCallback /*?*/ callback,
316316 ) {
317317 _sendPlatformMessage (name, data, _zonedPlatformMessageResponseCallback (callback));
318318 }
@@ -354,9 +354,9 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
354354 }
355355
356356 void _sendPlatformMessage (
357- String name,
358- ByteData data,
359- ui.PlatformMessageResponseCallback callback,
357+ String /*?*/ name,
358+ ByteData /*?*/ data,
359+ ui.PlatformMessageResponseCallback /*?*/ callback,
360360 ) {
361361 // In widget tests we want to bypass processing of platform messages.
362362 if (assertionsEnabled && ui.debugEmulateFlutterTesterEnvironment) {
@@ -546,7 +546,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
546546 /// * [RendererBinding] , the Flutter framework class which manages layout and
547547 /// painting.
548548 @override
549- void render (ui.Scene scene, [ui.FlutterView view]) {
549+ void render (ui.Scene /*!*/ scene, [ui.FlutterView view]) {
550550 if (experimentalUseSkia) {
551551 final LayerScene layerScene = scene;
552552 rasterizer.draw (layerScene.layerTree);
@@ -588,22 +588,15 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
588588 EngineSemanticsOwner .instance.updateSemantics (update);
589589 }
590590
591- /// The system-reported default locale of the device.
591+ /// We use the first locale in the [locales] list instead of the browser's
592+ /// built-in `navigator.language` because browsers do not agree on the
593+ /// implementation.
592594 ///
593- /// This establishes the language and formatting conventions that application
594- /// should, if possible, use to render their user interface.
595- ///
596- /// This is the first locale selected by the user and is the user's
597- /// primary locale (the locale the device UI is displayed in)
595+ /// See also:
598596 ///
599- /// This is equivalent to `locales.first` and will provide an empty non-null locale
600- /// if the [locales] list has not been set or is empty.
601- ui.Locale get locale {
602- if (configuration? .locales != null && configuration.locales.isNotEmpty) {
603- return locales.first;
604- }
605- return null ;
606- }
597+ /// * https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages,
598+ /// which explains browser quirks in the implementation notes.
599+ ui.Locale get locale => locales.first;
607600
608601 /// The full system-reported supported locales of the device.
609602 ///
@@ -650,6 +643,45 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
650643 _onLocaleChangedZone = Zone .current;
651644 }
652645
646+ /// The locale used when we fail to get the list from the browser.
647+ static const _defaultLocale = const ui.Locale ('en' , 'US' );
648+
649+ /// Sets locales to an empty list.
650+ ///
651+ /// The empty list is not a valid value for locales. This is only used for
652+ /// testing locale update logic.
653+ void debugResetLocales () {
654+ _configuration = _configuration.copyWith (locales: const < ui.Locale > []);
655+ }
656+
657+ // Called by DomRenderer when browser languages change.
658+ void _updateLocales () {
659+ _configuration = _configuration.copyWith (locales: parseBrowserLanguages ());
660+ }
661+
662+ static List <ui.Locale > parseBrowserLanguages () {
663+ // TODO(yjbanov): find a solution for IE
664+ final bool languagesFeatureMissing = ! js_util.hasProperty (html.window.navigator, 'languages' );
665+ if (languagesFeatureMissing || html.window.navigator.languages.isEmpty) {
666+ // To make it easier for the app code, let's not leave the locales list
667+ // empty. This way there's fewer corner cases for apps to handle.
668+ return const [_defaultLocale];
669+ }
670+
671+ final List <ui.Locale > locales = < ui.Locale > [];
672+ for (final String language in html.window.navigator.languages) {
673+ final List <String > parts = language.split ('-' );
674+ if (parts.length > 1 ) {
675+ locales.add (ui.Locale (parts.first, parts.last));
676+ } else {
677+ locales.add (ui.Locale (language));
678+ }
679+ }
680+
681+ assert (locales.isNotEmpty);
682+ return locales;
683+ }
684+
653685 /// Engine code should use this method instead of the callback directly.
654686 /// Otherwise zones won't work properly.
655687 void invokeOnLocaleChanged () {
0 commit comments