mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
New locale resolution algorithm to use full preferred locale list, include scriptCode in Locale. (#23583)
This commit is contained in:
parent
5071657e8e
commit
dfd0229560
@ -90,6 +90,7 @@ class CupertinoApp extends StatefulWidget {
|
||||
this.color,
|
||||
this.locale,
|
||||
this.localizationsDelegates,
|
||||
this.localeListResolutionCallback,
|
||||
this.localeResolutionCallback,
|
||||
this.supportedLocales = const <Locale>[Locale('en', 'US')],
|
||||
this.showPerformanceOverlay = false,
|
||||
@ -157,6 +158,11 @@ class CupertinoApp extends StatefulWidget {
|
||||
/// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
|
||||
final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
|
||||
///
|
||||
/// This callback is passed along to the [WidgetsApp] built by this widget.
|
||||
final LocaleListResolutionCallback localeListResolutionCallback;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.localeResolutionCallback}
|
||||
///
|
||||
/// This callback is passed along to the [WidgetsApp] built by this widget.
|
||||
@ -283,6 +289,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
|
||||
locale: widget.locale,
|
||||
localizationsDelegates: _localizationsDelegates,
|
||||
localeResolutionCallback: widget.localeResolutionCallback,
|
||||
localeListResolutionCallback: widget.localeListResolutionCallback,
|
||||
supportedLocales: widget.supportedLocales,
|
||||
showPerformanceOverlay: widget.showPerformanceOverlay,
|
||||
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
|
||||
|
||||
@ -98,6 +98,7 @@ class MaterialApp extends StatefulWidget {
|
||||
this.theme,
|
||||
this.locale,
|
||||
this.localizationsDelegates,
|
||||
this.localeListResolutionCallback,
|
||||
this.localeResolutionCallback,
|
||||
this.supportedLocales = const <Locale>[Locale('en', 'US')],
|
||||
this.debugShowMaterialGrid = false,
|
||||
@ -264,6 +265,11 @@ class MaterialApp extends StatefulWidget {
|
||||
/// <https://flutter.io/tutorials/internationalization/>.
|
||||
final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
|
||||
///
|
||||
/// This callback is passed along to the [WidgetsApp] built by this widget.
|
||||
final LocaleListResolutionCallback localeListResolutionCallback;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.localeResolutionCallback}
|
||||
///
|
||||
/// This callback is passed along to the [WidgetsApp] built by this widget.
|
||||
@ -423,6 +429,7 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
locale: widget.locale,
|
||||
localizationsDelegates: _localizationsDelegates,
|
||||
localeResolutionCallback: widget.localeResolutionCallback,
|
||||
localeListResolutionCallback: widget.localeListResolutionCallback,
|
||||
supportedLocales: widget.supportedLocales,
|
||||
showPerformanceOverlay: widget.showPerformanceOverlay,
|
||||
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:collection' show HashMap;
|
||||
import 'dart:ui' as ui show window;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -24,19 +25,50 @@ import 'widget_inspector.dart';
|
||||
|
||||
export 'dart:ui' show Locale;
|
||||
|
||||
/// The signature of [WidgetsApp.localeListResolutionCallback].
|
||||
///
|
||||
/// A [LocaleListResolutionCallback] is responsible for computing the locale of the app's
|
||||
/// [Localizations] object when the app starts and when user changes the list of
|
||||
/// locales for the device.
|
||||
///
|
||||
/// The [locales] list is the device's preferred locales when the app started, or the
|
||||
/// device's preferred locales the user selected after the app was started. This list
|
||||
/// is in order of preference. If this list is null or empty, then Flutter has not yet
|
||||
/// recieved the locale information from the platform. The [supportedLocales] parameter
|
||||
/// is just the value of [WidgetsApp.supportedLocales].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [LocaleResolutionCallback], which takes only one default locale (instead of a list)
|
||||
/// and is attempted only after this callback fails or is null. [LocaleListResolutionCallback]
|
||||
/// is recommended over [LocaleResolutionCallback].
|
||||
typedef LocaleListResolutionCallback = Locale Function(List<Locale> locales, Iterable<Locale> supportedLocales);
|
||||
|
||||
/// The signature of [WidgetsApp.localeResolutionCallback].
|
||||
///
|
||||
/// A `LocaleResolutionCallback` is responsible for computing the locale of the app's
|
||||
/// It is recommended to provide a [LocaleListResolutionCallback] instead of a
|
||||
/// [LocaleResolutionCallback] when possible, as [LocaleListResolutionCallback] as
|
||||
/// this callback only recieves a subset of the information provided
|
||||
/// in [LocaleListResolutionCallback].
|
||||
///
|
||||
/// A [LocaleResolutionCallback] is responsible for computing the locale of the app's
|
||||
/// [Localizations] object when the app starts and when user changes the default
|
||||
/// locale for the device.
|
||||
/// locale for the device after [LocaleListResolutionCallback] fails or is not provided.
|
||||
///
|
||||
/// This callback is also used if the app is created with a specific locale using
|
||||
/// the [new WidgetsApp] `locale` parameter.
|
||||
///
|
||||
/// The `locale` is either the value of [WidgetsApp.locale], or the device's
|
||||
/// locale when the app started, or the device locale the user selected after
|
||||
/// the app was started. The `supportedLocales` parameter is the value of
|
||||
/// The [locale] is either the value of [WidgetsApp.locale], or the device's default
|
||||
/// locale when the app started, or the device locale the user selected after the app
|
||||
/// was started. The default locale is the first locale in the list of preferred
|
||||
/// locales. If [locale] is null, then Flutter has not yet recieved the locale
|
||||
/// information from the platform. The [supportedLocales] parameter is just the value of
|
||||
/// [WidgetsApp.supportedLocales].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [LocaleListResolutionCallback], which takes a list of preferred locales (instead of one locale).
|
||||
/// Resolutions by [LocaleListResolutionCallback] take precedence over [LocaleResolutionCallback].
|
||||
typedef LocaleResolutionCallback = Locale Function(Locale locale, Iterable<Locale> supportedLocales);
|
||||
|
||||
/// The signature of [WidgetsApp.onGenerateTitle].
|
||||
@ -124,6 +156,7 @@ class WidgetsApp extends StatefulWidget {
|
||||
@required this.color,
|
||||
this.locale,
|
||||
this.localizationsDelegates,
|
||||
this.localeListResolutionCallback,
|
||||
this.localeResolutionCallback,
|
||||
this.supportedLocales = const <Locale>[Locale('en', 'US')],
|
||||
this.showPerformanceOverlay = false,
|
||||
@ -468,29 +501,49 @@ class WidgetsApp extends StatefulWidget {
|
||||
/// {@endtemplate}
|
||||
final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates;
|
||||
|
||||
/// {@template flutter.widgets.widgetsApp.localeResolutionCallback}
|
||||
/// {@template flutter.widgets.widgetsApp.localeListResolutionCallback}
|
||||
/// This callback is responsible for choosing the app's locale
|
||||
/// when the app is started, and when the user changes the
|
||||
/// device's locale.
|
||||
///
|
||||
/// The returned value becomes the locale of this app's [Localizations]
|
||||
/// widget. The callback's `locale` parameter is the device's locale when
|
||||
/// the app started, or the device locale the user selected after the app was
|
||||
/// started. The callback's `supportedLocales` parameter is just the value
|
||||
/// [supportedLocales].
|
||||
/// When a [localeListResolutionCallback] is provided, Flutter will first attempt to
|
||||
/// resolve the locale with the provided [localeListResolutionCallback]. If the
|
||||
/// callback or result is null, it will fallback to trying the [localeResolutionCallback].
|
||||
/// If both [localeResolutionCallback] and [localeListResolutionCallback] are left null
|
||||
/// or fail to resolve (return null), the [WidgetsApp.basicLocaleListResolution]
|
||||
/// fallback algorithm will be used.
|
||||
///
|
||||
/// If the callback is null or if it returns null then the resolved locale is:
|
||||
/// The priority of each available fallback is:
|
||||
///
|
||||
/// - The callback's `locale` parameter if it's equal to a supported locale.
|
||||
/// - The first supported locale with the same [Locale.languageCode] as the
|
||||
/// callback's `locale` parameter.
|
||||
/// - The first locale in [supportedLocales].
|
||||
/// 1. [localeListResolutionCallback] is attempted first.
|
||||
/// 2. [localeResolutionCallback] is attempted second.
|
||||
/// 3. Flutter's [WidgetsApp.basicLocaleListResolution] algorithm is attempted last.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// This callback considers the entire list of preferred locales.
|
||||
///
|
||||
/// This algorithm should be able to handle a null or empty list of preferred locales,
|
||||
/// which indicates Flutter has not yet recieved locale information from the platform.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialApp.localeResolutionCallback], which sets the callback of the
|
||||
/// [WidgetsApp] it creates.
|
||||
final LocaleListResolutionCallback localeListResolutionCallback;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
|
||||
///
|
||||
/// This callback considers only the default locale, which is the first locale
|
||||
/// in the preferred locales list. It is preferred to set [localeListResolutionCallback]
|
||||
/// over [localeResolutionCallback] as it provides the full preferred locales list.
|
||||
///
|
||||
/// This algorithm should be able to handle a null locale, which indicates
|
||||
/// Flutter has not yet recieved locale information from the platform.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialApp.localeListResolutionCallback], which sets the callback of the
|
||||
/// [WidgetsApp] it creates.
|
||||
final LocaleResolutionCallback localeResolutionCallback;
|
||||
|
||||
/// {@template flutter.widgets.widgetsApp.supportedLocales}
|
||||
@ -607,7 +660,7 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateNavigator();
|
||||
_locale = _resolveLocale(ui.window.locale, widget.supportedLocales);
|
||||
_locale = _resolveLocales(ui.window.locales, widget.supportedLocales);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@ -717,35 +770,147 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
||||
|
||||
// LOCALIZATION
|
||||
|
||||
/// This is the resolved locale, and is one of the supportedLocales.
|
||||
Locale _locale;
|
||||
|
||||
Locale _resolveLocale(Locale newLocale, Iterable<Locale> supportedLocales) {
|
||||
if (widget.localeResolutionCallback != null) {
|
||||
final Locale locale = widget.localeResolutionCallback(newLocale, widget.supportedLocales);
|
||||
Locale _resolveLocales(List<Locale> preferredLocales, Iterable<Locale> supportedLocales) {
|
||||
// Attempt to use localeListResolutionCallback.
|
||||
if (widget.localeListResolutionCallback != null) {
|
||||
final Locale locale = widget.localeListResolutionCallback(preferredLocales, widget.supportedLocales);
|
||||
if (locale != null)
|
||||
return locale;
|
||||
}
|
||||
// newLocale can be null when called before the platform has had a chance to
|
||||
// initialize the locales. We default to the first supported locale.
|
||||
if (newLocale == null) {
|
||||
// localeListResolutionCallback failed, falling back to localeResolutionCallback.
|
||||
if (widget.localeResolutionCallback != null) {
|
||||
final Locale locale = widget.localeResolutionCallback(preferredLocales.first, widget.supportedLocales);
|
||||
if (locale != null)
|
||||
return locale;
|
||||
}
|
||||
// Both callbacks failed, falling back to default algorithm.
|
||||
return basicLocaleListResolution(preferredLocales, supportedLocales);
|
||||
}
|
||||
|
||||
/// The default locale resolution algorithm.
|
||||
///
|
||||
/// Custom resolution algorithms can be provided through [WidgetsApp.localeListResolutionCallback]
|
||||
/// or [WidgetsApp.localeResolutionCallback].
|
||||
///
|
||||
/// When no custom locale resolition algorithms are provided or if both fail to resolve,
|
||||
/// Flutter will default to calling this algorithm.
|
||||
///
|
||||
/// This algorithm prioritizes speed at the cost of slightly less appropriate
|
||||
/// resolutions for edge cases.
|
||||
///
|
||||
/// This algorithm will resolve to the earliest locale in [preferredLocales] that
|
||||
/// matches the most fields, prioritizing in the order of perfect match,
|
||||
/// languageCode+countryCode, languageCode+scriptCode, languageCode-only.
|
||||
///
|
||||
/// In the case where a locale is matched by languageCode-only and is not the
|
||||
/// default (first) locale, the next locale in preferredLocales with a
|
||||
/// perfect match can supercede the languageCode-only match if it exists.
|
||||
///
|
||||
/// When a preferredLocale matches more than one supported locale, it will resolve
|
||||
/// to the first matching locale listed in the supportedLocales.
|
||||
///
|
||||
/// When all [preferredLocales] have been exhausted without a match, the first countryCode only
|
||||
/// match will be returned.
|
||||
///
|
||||
/// When no match at all is found, the first (default) locale in [supportedLocales] will be
|
||||
/// returned.
|
||||
///
|
||||
/// This algorithm does not take language distance (how similar languages are to each other)
|
||||
/// into account, and will not handle edge cases such as resolving `de` to `fr` rather than `zh`
|
||||
/// when `de` is not supported and `zh` is listed before `fr` (German is closer to French
|
||||
/// than Chinese).
|
||||
static Locale basicLocaleListResolution(List<Locale> preferredLocales, Iterable<Locale> supportedLocales) {
|
||||
// preferredLocales can be null when called before the platform has had a chance to
|
||||
// initialize the locales. Platforms without locale passing support will provide an empty list.
|
||||
// We default to the first supported locale in these cases.
|
||||
if (preferredLocales == null || preferredLocales.isEmpty) {
|
||||
return supportedLocales.first;
|
||||
}
|
||||
|
||||
Locale matchesLanguageCode;
|
||||
// Hash the supported locales because apps can support many locales and would
|
||||
// be expensive to search through them many times.
|
||||
final Map<String, Locale> allSupportedLocales = HashMap<String, Locale>();
|
||||
final Map<String, Locale> languageAndCountryLocales = HashMap<String, Locale>();
|
||||
final Map<String, Locale> languageAndScriptLocales = HashMap<String, Locale>();
|
||||
final Map<String, Locale> languageLocales = HashMap<String, Locale>();
|
||||
final Map<String, Locale> countryLocales = HashMap<String, Locale>();
|
||||
for (Locale locale in supportedLocales) {
|
||||
if (locale == newLocale)
|
||||
return newLocale;
|
||||
if (locale.languageCode == newLocale.languageCode)
|
||||
matchesLanguageCode ??= locale;
|
||||
allSupportedLocales['${locale.languageCode}_${locale.scriptCode}_${locale.countryCode}'] ??= locale;
|
||||
languageAndScriptLocales['${locale.languageCode}_${locale.scriptCode}'] ??= locale;
|
||||
languageAndCountryLocales['${locale.languageCode}_${locale.countryCode}'] ??= locale;
|
||||
languageLocales[locale.languageCode] ??= locale;
|
||||
countryLocales[locale.countryCode] ??= locale;
|
||||
}
|
||||
return matchesLanguageCode ?? supportedLocales.first;
|
||||
|
||||
// Since languageCode-only matches are possibly low quality, we don't return
|
||||
// it instantly when we find such a match. We check to see if the next
|
||||
// preferred locale in the list has a high accuracy match, and only return
|
||||
// the languageCode-only match when a higher accuracy match in the next
|
||||
// preferred locale cannot be found.
|
||||
Locale matchesLanguageCode;
|
||||
Locale matchesCountryCode;
|
||||
// Loop over user's preferred locales
|
||||
for (int localeIndex = 0; localeIndex < preferredLocales.length; localeIndex += 1) {
|
||||
final Locale userLocale = preferredLocales[localeIndex];
|
||||
// Look for perfect match.
|
||||
if (allSupportedLocales.containsKey('${userLocale.languageCode}_${userLocale.scriptCode}_${userLocale.countryCode}')) {
|
||||
return userLocale;
|
||||
}
|
||||
// Look for language+script match.
|
||||
if (userLocale.scriptCode != null) {
|
||||
final Locale match = languageAndScriptLocales['${userLocale.languageCode}_${userLocale.scriptCode}'];
|
||||
if (match != null) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
// Look for language+country match.
|
||||
if (userLocale.countryCode != null) {
|
||||
final Locale match = languageAndCountryLocales['${userLocale.languageCode}_${userLocale.countryCode}'];
|
||||
if (match != null) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
// If there was a languageCode-only match in the previous iteration's higher
|
||||
// ranked preferred locale, we return it if the current userLocale does not
|
||||
// have a better match.
|
||||
if (matchesLanguageCode != null) {
|
||||
return matchesLanguageCode;
|
||||
}
|
||||
// Look and store language-only match.
|
||||
Locale match = languageLocales[userLocale.languageCode];
|
||||
if (match != null) {
|
||||
matchesLanguageCode = match;
|
||||
// Since first (default) locale is usually highly preferred, we will allow
|
||||
// a languageCode-only match to be instantly matched. If the next preferred
|
||||
// languageCode is the same, we defer hastily returning until the next iteration
|
||||
// since at worst it is the same and at best an improved match.
|
||||
if (localeIndex == 0 &&
|
||||
!(localeIndex + 1 < preferredLocales.length && preferredLocales[localeIndex + 1].languageCode == userLocale.languageCode)) {
|
||||
return matchesLanguageCode;
|
||||
}
|
||||
}
|
||||
// countryCode-only match. When all else except default supported locale fails,
|
||||
// attempt to match by country only, as a user is likely to be familar with a
|
||||
// language from their listed country.
|
||||
if (matchesCountryCode == null && userLocale.countryCode != null) {
|
||||
match = countryLocales[userLocale.countryCode];
|
||||
if (match != null) {
|
||||
matchesCountryCode = match;
|
||||
}
|
||||
}
|
||||
}
|
||||
// When there is no languageCode-only match. Fallback to matching countryCode only. Country
|
||||
// fallback only applies on iOS. When there is no countryCode-only match, we return first
|
||||
// suported locale.
|
||||
final Locale resolvedLocale = matchesLanguageCode ?? matchesCountryCode ?? supportedLocales.first;
|
||||
return resolvedLocale;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeLocale(Locale locale) {
|
||||
if (locale == _locale)
|
||||
return;
|
||||
final Locale newLocale = _resolveLocale(locale, widget.supportedLocales);
|
||||
void didChangeLocales(List<Locale> locales) {
|
||||
final Locale newLocale = _resolveLocales(locales, widget.supportedLocales);
|
||||
if (newLocale != _locale) {
|
||||
setState(() {
|
||||
_locale = newLocale;
|
||||
@ -950,7 +1115,7 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
||||
}
|
||||
|
||||
final Locale appLocale = widget.locale != null
|
||||
? _resolveLocale(widget.locale, widget.supportedLocales)
|
||||
? _resolveLocales(<Locale>[widget.locale], widget.supportedLocales)
|
||||
: _locale;
|
||||
|
||||
assert(_debugCheckLocalizations(appLocale));
|
||||
|
||||
@ -219,7 +219,7 @@ abstract class WidgetsBindingObserver {
|
||||
/// settings.
|
||||
///
|
||||
/// This method exposes notifications from [Window.onLocaleChanged].
|
||||
void didChangeLocale(Locale locale) { }
|
||||
void didChangeLocales(List<Locale> locale) { }
|
||||
|
||||
/// Called when the system puts the app in the background or returns
|
||||
/// the app to the foreground.
|
||||
@ -413,20 +413,20 @@ mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererB
|
||||
@protected
|
||||
@mustCallSuper
|
||||
void handleLocaleChanged() {
|
||||
dispatchLocaleChanged(ui.window.locale);
|
||||
dispatchLocalesChanged(ui.window.locales);
|
||||
}
|
||||
|
||||
/// Notify all the observers that the locale has changed (using
|
||||
/// [WidgetsBindingObserver.didChangeLocale]), giving them the
|
||||
/// `locale` argument.
|
||||
/// [WidgetsBindingObserver.didChangeLocales]), giving them the
|
||||
/// `locales` argument.
|
||||
///
|
||||
/// This is called by [handleLocaleChanged] when the [Window.onLocaleChanged]
|
||||
/// notification is received.
|
||||
@protected
|
||||
@mustCallSuper
|
||||
void dispatchLocaleChanged(Locale locale) {
|
||||
void dispatchLocalesChanged(List<Locale> locales) {
|
||||
for (WidgetsBindingObserver observer in _observers)
|
||||
observer.didChangeLocale(locale);
|
||||
observer.didChangeLocales(locales);
|
||||
}
|
||||
|
||||
/// Notify all the observers that the active set of [AccessibilityFeatures]
|
||||
|
||||
@ -745,4 +745,673 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_CN'), findsOneWidget);
|
||||
});
|
||||
|
||||
// Example from http://unicode.org/reports/tr35/#LanguageMatching
|
||||
testWidgets('WidgetsApp Unicode tr35 1', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
supportedLocales: const <Locale>[
|
||||
Locale('de'),
|
||||
Locale('fr'),
|
||||
Locale('ja'),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
final Locale locale = Localizations.localeOf(context);
|
||||
return Text('$locale');
|
||||
}
|
||||
)
|
||||
);
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'de', countryCode: 'AT'),
|
||||
Locale.fromSubtags(languageCode: 'fr'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('de'), findsOneWidget);
|
||||
});
|
||||
|
||||
// Examples from http://unicode.org/reports/tr35/#LanguageMatching
|
||||
testWidgets('WidgetsApp Unicode tr35 2', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
supportedLocales: const <Locale>[
|
||||
Locale('ja', 'JP'),
|
||||
Locale('de'),
|
||||
Locale('zh', 'TW'),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
final Locale locale = Localizations.localeOf(context);
|
||||
return Text('$locale');
|
||||
}
|
||||
)
|
||||
);
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_TW'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'de'),
|
||||
Locale.fromSubtags(languageCode: 'fr'),
|
||||
Locale.fromSubtags(languageCode: 'de', countryCode: 'SW'),
|
||||
Locale.fromSubtags(languageCode: 'it'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('de'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('WidgetsApp EdgeCase Chinese', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
supportedLocales: const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
final Locale locale = Localizations.localeOf(context);
|
||||
return Text('$locale');
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'de'),
|
||||
Locale.fromSubtags(languageCode: 'fr'),
|
||||
Locale.fromSubtags(languageCode: 'de', countryCode: 'SW'),
|
||||
Locale.fromSubtags(languageCode: 'zh'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'US'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_TW'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_TW'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_TW'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'TW'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_TW'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'HK'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_HK'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'HK'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hans_CN'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'CN'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hans_CN'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh'), findsOneWidget);
|
||||
|
||||
// This behavior is up to the implementer to decide if a perfect scriptCode match
|
||||
// is better than a countryCode match.
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'CN'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'CN'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_TW'), findsOneWidget);
|
||||
|
||||
|
||||
// languageCode only match is not enough to prevent resolving a perfect match
|
||||
// further down the preferredLocales list.
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hans_CN'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'JP'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hans_CN'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hans_CN'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hans_CN'), findsOneWidget);
|
||||
|
||||
// When no language match, we try for country only, since it is likely users are
|
||||
// at least familiar with their country's language. This is a possible case only
|
||||
// on iOS, where countryCode can be selected independently from language and script.
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', scriptCode: 'Hans', countryCode: 'TW'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_TW'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'TW'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_TW'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'HK'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_HK'), findsOneWidget);
|
||||
});
|
||||
|
||||
// Same as 'WidgetsApp EdgeCase Chinese' test except the supportedLocales order is
|
||||
// reversed.
|
||||
testWidgets('WidgetsApp EdgeCase ReverseChinese', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
supportedLocales: const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),
|
||||
Locale.fromSubtags(languageCode: 'zh'),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
final Locale locale = Localizations.localeOf(context);
|
||||
return Text('$locale');
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'de'),
|
||||
Locale.fromSubtags(languageCode: 'fr'),
|
||||
Locale.fromSubtags(languageCode: 'de', countryCode: 'SW'),
|
||||
Locale.fromSubtags(languageCode: 'zh'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'US'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_HK'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_HK'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_TW'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'TW'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_TW'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'HK'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_HK'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'HK'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hans_CN'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'CN'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hans_CN'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_HK'), findsOneWidget);
|
||||
|
||||
// This behavior is up to the implementer to decide if a perfect scriptCode match
|
||||
// is better than a countryCode match.
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'CN'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'CN'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_HK'), findsOneWidget);
|
||||
|
||||
// languageCode only match is not enough to prevent resolving a perfect match
|
||||
// further down the preferredLocales list.
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hans_CN'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'JP'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hans_CN'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hans_CN'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hans_CN'), findsOneWidget);
|
||||
|
||||
// When no language match, we try for country only, since it is likely users are
|
||||
// at least familiar with their country's language. This is a possible case only
|
||||
// on iOS, where countryCode can be selected independently from language and script.
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', scriptCode: 'Hans', countryCode: 'TW'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_TW'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'TW'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_TW'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'HK'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('zh_Hant_HK'), findsOneWidget);
|
||||
});
|
||||
|
||||
// Examples from https://developer.android.com/guide/topics/resources/multilingual-support
|
||||
testWidgets('WidgetsApp Android', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
supportedLocales: const <Locale>[
|
||||
Locale('en'),
|
||||
Locale('de', 'DE'),
|
||||
Locale('es', 'ES'),
|
||||
Locale('fr', 'FR'),
|
||||
Locale('it', 'IT'),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
final Locale locale = Localizations.localeOf(context);
|
||||
return Text('$locale');
|
||||
}
|
||||
)
|
||||
);
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'fr', countryCode: 'CH'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('fr_FR'), findsOneWidget);
|
||||
});
|
||||
|
||||
// Examples from https://developer.android.com/guide/topics/resources/multilingual-support
|
||||
testWidgets('WidgetsApp Android', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
supportedLocales: const <Locale>[
|
||||
Locale('en'),
|
||||
Locale('de', 'DE'),
|
||||
Locale('es', 'ES'),
|
||||
Locale('it', 'IT'),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
final Locale locale = Localizations.localeOf(context);
|
||||
return Text('$locale');
|
||||
}
|
||||
)
|
||||
);
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'fr', countryCode: 'CH'),
|
||||
Locale.fromSubtags(languageCode: 'it', countryCode: 'CH'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('it_IT'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('WidgetsApp Country-only fallback', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
supportedLocales: const <Locale>[
|
||||
Locale('en', 'US'),
|
||||
Locale('de', 'DE'),
|
||||
Locale('de', 'AU'),
|
||||
Locale('de', 'LU'),
|
||||
Locale('de', 'CH'),
|
||||
Locale('es', 'ES'),
|
||||
Locale('es', 'US'),
|
||||
Locale('it', 'IT'),
|
||||
Locale('zh', 'CN'),
|
||||
Locale('zh', 'TW'),
|
||||
Locale('fr', 'FR'),
|
||||
Locale('br', 'FR'),
|
||||
Locale('pt', 'BR'),
|
||||
Locale('pt', 'PT'),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
final Locale locale = Localizations.localeOf(context);
|
||||
return Text('$locale');
|
||||
}
|
||||
)
|
||||
);
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'ar', countryCode: 'CH'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('de_CH'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'ar', countryCode: 'FR'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('fr_FR'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'ar', countryCode: 'US'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_US'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'es', countryCode: 'US'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('es_US'), findsOneWidget);
|
||||
|
||||
// Strongly prefer matching first locale even if next one is perfect.
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'pt'),
|
||||
Locale.fromSubtags(languageCode: 'pt', countryCode: 'PT'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('pt_PT'), findsOneWidget);
|
||||
|
||||
// Don't country match with any other available match. This behavior is
|
||||
// up for reconsideration.
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'ar', countryCode: 'BR'),
|
||||
Locale.fromSubtags(languageCode: 'pt'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('pt_BR'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'ar', countryCode: 'BR'),
|
||||
Locale.fromSubtags(languageCode: 'pt', countryCode: 'PT'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('pt_PT'), findsOneWidget);
|
||||
});
|
||||
|
||||
// Simulates a Chinese-default app that supports english in Canada but not
|
||||
// French. French-Canadian users should get 'en_CA' instead of Chinese.
|
||||
testWidgets('WidgetsApp Multilingual country', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
supportedLocales: const <Locale>[
|
||||
Locale('zh', 'CN'),
|
||||
Locale('en', 'CA'),
|
||||
Locale('en', 'US'),
|
||||
Locale('en', 'AU'),
|
||||
Locale('de', 'DE'),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
final Locale locale = Localizations.localeOf(context);
|
||||
return Text('$locale');
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'fr', countryCode: 'CA'),
|
||||
Locale.fromSubtags(languageCode: 'fr'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_CA'), findsOneWidget);
|
||||
});
|
||||
|
||||
|
||||
testWidgets('WidgetsApp Common cases', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
// Decently well localized app.
|
||||
supportedLocales: const <Locale>[
|
||||
Locale('en', 'US'),
|
||||
Locale('en', 'GB'),
|
||||
Locale('en', 'AU'),
|
||||
Locale('en', 'CA'),
|
||||
Locale('zh', 'CN'),
|
||||
Locale('zh', 'TW'),
|
||||
Locale('de', 'DE'),
|
||||
Locale('de', 'CH'),
|
||||
Locale('es', 'MX'),
|
||||
Locale('es', 'ES'),
|
||||
Locale('es', 'AR'),
|
||||
Locale('es', 'CO'),
|
||||
Locale('ru', 'RU'),
|
||||
Locale('fr', 'FR'),
|
||||
Locale('fr', 'CA'),
|
||||
Locale('ar', 'SA'),
|
||||
Locale('ar', 'EG'),
|
||||
Locale('ar', 'IQ'),
|
||||
Locale('ar', 'MA'),
|
||||
Locale('af'),
|
||||
Locale('bg'),
|
||||
Locale('nl', 'NL'),
|
||||
Locale('pl'),
|
||||
Locale('cs'),
|
||||
Locale('fa'),
|
||||
Locale('el'),
|
||||
Locale('he'),
|
||||
Locale('hi'),
|
||||
Locale('pa'),
|
||||
Locale('ta'),
|
||||
Locale('id'),
|
||||
Locale('it', 'IT'),
|
||||
Locale('ja'),
|
||||
Locale('ko'),
|
||||
Locale('ms'),
|
||||
Locale('mn'),
|
||||
Locale('pt', 'BR'),
|
||||
Locale('pt', 'PT'),
|
||||
Locale('sv', 'SE'),
|
||||
Locale('th'),
|
||||
Locale('tr'),
|
||||
Locale('vi'),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
final Locale locale = Localizations.localeOf(context);
|
||||
return Text('$locale');
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_US'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_US'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'CA'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_CA'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'AU'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_AU'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'ar', countryCode: 'CH'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('ar_SA'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'ar'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('ar_SA'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'ar', countryCode: 'IQ'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('ar_IQ'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'es', countryCode: 'ES'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('es_ES'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'es'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('es_MX'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'pa', countryCode: 'US'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('pa'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'hi', countryCode: 'IN'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('hi'), findsOneWidget);
|
||||
|
||||
// Multiple preferred locales:
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'NZ'),
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'AU'),
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'GB'),
|
||||
Locale.fromSubtags(languageCode: 'en'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_AU'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'ab'),
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'NZ'),
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'AU'),
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'GB'),
|
||||
Locale.fromSubtags(languageCode: 'en'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_AU'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'NZ'),
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'PH'),
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'ZA'),
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'CB'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_US'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'CA'),
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'AU'),
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'GB'),
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_CA'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'da'),
|
||||
Locale.fromSubtags(languageCode: 'en'),
|
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'CA'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_CA'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'da'),
|
||||
Locale.fromSubtags(languageCode: 'fo'),
|
||||
Locale.fromSubtags(languageCode: 'hr'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_US'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocales(const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'da'),
|
||||
Locale.fromSubtags(languageCode: 'fo'),
|
||||
Locale.fromSubtags(languageCode: 'hr', countryCode: 'CA'),]
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('en_CA'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
@ -225,13 +225,25 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
||||
Duration additionalTime = const Duration(milliseconds: 250),
|
||||
});
|
||||
|
||||
/// Artificially calls dispatchLocaleChanged on the Widget binding,
|
||||
/// Artificially calls dispatchLocalesChanged on the Widget binding,
|
||||
/// then flushes microtasks.
|
||||
///
|
||||
/// Passes only one single Locale. Use [setLocales] to pass a full preferred
|
||||
/// locales list.
|
||||
Future<void> setLocale(String languageCode, String countryCode) {
|
||||
return TestAsyncUtils.guard<void>(() async {
|
||||
assert(inTest);
|
||||
final Locale locale = Locale(languageCode, countryCode);
|
||||
dispatchLocaleChanged(locale);
|
||||
final Locale locale = Locale(languageCode, countryCode == '' ? null : countryCode);
|
||||
dispatchLocalesChanged(<Locale>[locale]);
|
||||
});
|
||||
}
|
||||
|
||||
/// Artificially calls dispatchLocalesChanged on the Widget binding,
|
||||
/// then flushes microtasks.
|
||||
Future<void> setLocales(List<Locale> locales) {
|
||||
return TestAsyncUtils.guard<void>(() async {
|
||||
assert(inTest);
|
||||
dispatchLocalesChanged(locales);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user