Commit 9fb6fd81 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Localizations overrides (#12094)

parent dc472386
......@@ -75,19 +75,15 @@ String generateLocalizationsMap() {
/// This variable is used by [MaterialLocalizations].
const Map<String, Map<String, String>> localizations = const <String, Map<String, String>> {''');
final String lastLocale = localeToResources.keys.last;
for (String locale in localeToResources.keys.toList()..sort()) {
output.writeln(' "$locale": const <String, String>{');
final Map<String, String> resources = localeToResources[locale];
final String lastName = resources.keys.last;
for (String name in resources.keys) {
final String comma = name == lastName ? "" : ",";
final String value = generateString(resources[name]);
output.writeln(' "$name": $value$comma');
output.writeln(' "$name": $value,');
}
final String comma = locale == lastLocale ? "" : ",";
output.writeln(' }$comma');
output.writeln(' },');
}
output.writeln('};');
......
......@@ -234,6 +234,61 @@ class MaterialApp extends StatefulWidget {
///
/// The delegates collectively define all of the localized resources
/// for this application's [Localizations] widget.
///
/// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations]
/// are included automatically. Apps can provide their own versions of these
/// localizations by creating implementations of
/// [LocalizationsDelegate<WidgetLocalizations>] or
/// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return
/// custom versions of [WidgetLocalizations] or [MaterialLocalizations].
///
/// For example: to add support to [MaterialLocalizations] for a
/// locale it doesn't already support, say `const Locale('foo', 'BR')`,
/// one could just extend [DefaultMaterialLocalizations]:
///
/// ```dart
/// class FooLocalizations extends DefaultMaterialLocalizations {
/// FooLocalizations(Locale locale) : super(locale);
/// @override
/// String get okButtonLabel {
/// if (locale == const Locale('foo', 'BR'))
/// return 'foo';
/// return super.okButtonLabel;
/// }
/// }
///
/// ```
///
/// A `FooLocalizationsDelegate` is essentially just a method that constructs
/// a `FooLocalizations` object. We return a [SynchronousFuture] here because
/// no asynchronous work takes place upon "loading" the localizations object.
///
/// ```dart
/// class FooLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
/// const FooLocalizationsDelegate();
/// @override
/// Future<FooLocalizations> load(Locale locale) {
/// return new SynchronousFuture(new FooLocalizations(locale));
/// }
/// @override
/// bool shouldReload(FooLocalizationsDelegate old) => false;
/// }
/// ```
///
/// Constructing a [MaterialApp] with a `FooLocalizationsDelegate` overrides
/// the automatically included delegate for [MaterialLocalizations] because
/// only the first delegate of each [LocalizationsDelegate.type] is used and
/// the automatically included delegates are added to the end of the app's
/// [localizationsDelegates] list.
///
/// ```dart
/// new MaterialApp(
/// localizationsDelegates: [
/// const FooLocalizationsDelegate(),
/// ],
/// // ...
/// )
/// ```
final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates;
/// This callback is responsible for choosing the app's locale
......@@ -379,11 +434,14 @@ class _MaterialAppState extends State<MaterialApp> {
}
// Combine the Localizations for Material with the ones contributed
// by the localizationsDelegates parameter, if any.
// by the localizationsDelegates parameter, if any. Only the first delegate
// of a particular LocalizationsDelegate.type is loaded so the
// localizationsDelegate parameter can be used to override
// _MaterialLocalizationsDelegate.
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
yield const _MaterialLocalizationsDelegate(); // TODO(ianh): make this configurable
if (widget.localizationsDelegates != null)
yield* widget.localizationsDelegates;
yield const _MaterialLocalizationsDelegate();
}
RectTween _createRectTween(Rect begin, Rect end) {
......
......@@ -4,7 +4,7 @@
// This file has been automatically generated. Please do not edit it manually.
// To regenerate the file, use:
// dart dev/tools/gen_localizations.dart lib/src/material/i18n material
// dart dev/tools/gen_localizations.dart packages/flutter/lib/src/material/i18n material
/// Maps from [Locale.languageCode] to a map that contains the localized strings
/// for that locale.
......@@ -36,7 +36,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"selectAllButtonLabel": r"اختر الكل",
"viewLicensesButtonLabel": r"عرض التراخيص",
"anteMeridiemAbbreviation": r"ص",
"postMeridiemAbbreviation": r"م"
"postMeridiemAbbreviation": r"م",
},
"de": const <String, String>{
"timeOfDayFormat": r"HH:mm",
......@@ -63,7 +63,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"okButtonLabel": r"OK",
"pasteButtonLabel": r"EINFÜGEN",
"selectAllButtonLabel": r"ALLES AUSWÄHLEN",
"viewLicensesButtonLabel": r"LIZENZEN ANZEIGEN"
"viewLicensesButtonLabel": r"LIZENZEN ANZEIGEN",
},
"en": const <String, String>{
"timeOfDayFormat": r"h:mm a",
......@@ -92,16 +92,16 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"selectAllButtonLabel": r"SELECT ALL",
"viewLicensesButtonLabel": r"VIEW LICENSES",
"anteMeridiemAbbreviation": r"AM",
"postMeridiemAbbreviation": r"PM"
"postMeridiemAbbreviation": r"PM",
},
"en_GB": const <String, String>{
"timeOfDayFormat": r"HH:mm"
"timeOfDayFormat": r"HH:mm",
},
"en_IE": const <String, String>{
"timeOfDayFormat": r"HH:mm"
"timeOfDayFormat": r"HH:mm",
},
"en_ZA": const <String, String>{
"timeOfDayFormat": r"HH:mm"
"timeOfDayFormat": r"HH:mm",
},
"es": const <String, String>{
"timeOfDayFormat": r"H:mm",
......@@ -128,10 +128,10 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"okButtonLabel": r"OK",
"pasteButtonLabel": r"PEGAR",
"selectAllButtonLabel": r"SELECCIONAR TODO",
"viewLicensesButtonLabel": r"VER LICENCIAS"
"viewLicensesButtonLabel": r"VER LICENCIAS",
},
"es_US": const <String, String>{
"timeOfDayFormat": r"h:mm a"
"timeOfDayFormat": r"h:mm a",
},
"fa": const <String, String>{
"timeOfDayFormat": r"H:mm",
......@@ -156,7 +156,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"okButtonLabel": r"تایید",
"pasteButtonLabel": r"چسباندن",
"selectAllButtonLabel": r"انتخاب همه",
"viewLicensesButtonLabel": r"مشاهده مجوز"
"viewLicensesButtonLabel": r"مشاهده مجوز",
},
"fr": const <String, String>{
"timeOfDayFormat": r"HH:mm",
......@@ -183,10 +183,10 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"okButtonLabel": r"OK",
"pasteButtonLabel": r"COLLER",
"selectAllButtonLabel": r"TOUT SÉLECTIONNER",
"viewLicensesButtonLabel": r"AFFICHER LES LICENCES"
"viewLicensesButtonLabel": r"AFFICHER LES LICENCES",
},
"fr_CA": const <String, String>{
"timeOfDayFormat": r"HH 'h' mm"
"timeOfDayFormat": r"HH 'h' mm",
},
"he": const <String, String>{
"timeOfDayFormat": r"H:mm",
......@@ -211,7 +211,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"okButtonLabel": r"בסדר",
"pasteButtonLabel": r"הדבק",
"selectAllButtonLabel": r"בחר הכל",
"viewLicensesButtonLabel": r"ראה רישיונות"
"viewLicensesButtonLabel": r"ראה רישיונות",
},
"it": const <String, String>{
"timeOfDayFormat": r"HH:mm",
......@@ -236,7 +236,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"okButtonLabel": r"OK",
"pasteButtonLabel": r"INCOLLA",
"selectAllButtonLabel": r"SELEZIONA TUTTO",
"viewLicensesButtonLabel": r"VEDI LE LICENZE"
"viewLicensesButtonLabel": r"VEDI LE LICENZE",
},
"ja": const <String, String>{
"timeOfDayFormat": r"H:mm",
......@@ -261,7 +261,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"okButtonLabel": r"OK",
"pasteButtonLabel": r"貼付け",
"selectAllButtonLabel": r"全選択",
"viewLicensesButtonLabel": r"ライセンス表記"
"viewLicensesButtonLabel": r"ライセンス表記",
},
"ps": const <String, String>{
"timeOfDayFormat": r"HH:mm",
......@@ -286,7 +286,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"okButtonLabel": r"سمه ده",
"pasteButtonLabel": r"پیټ کړئ",
"selectAllButtonLabel": r"غوره کړئ",
"viewLicensesButtonLabel": r"لیدلس وګورئ"
"viewLicensesButtonLabel": r"لیدلس وګورئ",
},
"pt": const <String, String>{
"timeOfDayFormat": r"HH:mm",
......@@ -311,7 +311,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"okButtonLabel": r"OK",
"pasteButtonLabel": r"COLAR",
"selectAllButtonLabel": r"SELECIONAR TUDO",
"viewLicensesButtonLabel": r"VER LICENÇAS"
"viewLicensesButtonLabel": r"VER LICENÇAS",
},
"ru": const <String, String>{
"timeOfDayFormat": r"H:mm",
......@@ -336,7 +336,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"okButtonLabel": r"ОК",
"pasteButtonLabel": r"Паст",
"selectAllButtonLabel": r"Выбрать все",
"viewLicensesButtonLabel": r"ПРОСМОТРЕТЬ ЛИЦЕНЗИИ"
"viewLicensesButtonLabel": r"ПРОСМОТРЕТЬ ЛИЦЕНЗИИ",
},
"sd": const <String, String>{
"timeOfDayFormat": r"HH:mm",
......@@ -361,7 +361,7 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"okButtonLabel": r"ٺيڪ آهي",
"pasteButtonLabel": r"پيسٽ ڪريو",
"selectAllButtonLabel": r"سڀ چونڊيو",
"viewLicensesButtonLabel": r"لائسنس ڏسو"
"viewLicensesButtonLabel": r"لائسنس ڏسو",
},
"ur": const <String, String>{
"timeOfDayFormat": r"h:mm a",
......@@ -388,12 +388,12 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"selectAllButtonLabel": r"تکاپیمام منتخب کریں",
"viewLicensesButtonLabel": r"لائسنس دیکھیں",
"anteMeridiemAbbreviation": r"AM",
"postMeridiemAbbreviation": r"PM"
"postMeridiemAbbreviation": r"PM",
},
"zh": const <String, String>{
"timeOfDayFormat": r"ah:mm",
"openAppDrawerTooltip": r"打开导航菜单",
"backButtonTooltip": r"背部",
"backButtonTooltip": r"返回",
"closeButtonTooltip": r"关",
"nextMonthTooltip": r"-下月就29了。",
"previousMonthTooltip": r"前一个月",
......@@ -415,7 +415,6 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"selectAllButtonLabel": r"全选",
"viewLicensesButtonLabel": r"查看许可证",
"anteMeridiemAbbreviation": r"上午",
"postMeridiemAbbreviation": r"下午"
}
"postMeridiemAbbreviation": r"下午",
},
};
......@@ -22,7 +22,6 @@
"pasteButtonLabel": "粘贴",
"selectAllButtonLabel": "全选",
"viewLicensesButtonLabel": "查看许可证",
"backButtonTooltip": "背部",
"closeButtonTooltip": "关",
"nextMonthTooltip": "-下月就29了。",
"previousMonthTooltip": "前一个月",
......
......@@ -116,24 +116,20 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
///
/// [LocalizationsDelegate] implementations typically call the static [load]
/// function, rather than constructing this class directly.
factory DefaultMaterialLocalizations(Locale locale) {
DefaultMaterialLocalizations(this.locale) {
assert(locale != null);
final Map<String, String> result = <String, String>{};
if (localizations.containsKey(locale.languageCode))
result.addAll(localizations[locale.languageCode]);
if (localizations.containsKey(locale.toString()))
result.addAll(localizations[locale.toString()]);
return new DefaultMaterialLocalizations._(locale, result);
_nameToValue.addAll(localizations[locale.languageCode]);
if (localizations.containsKey(_localeName))
_nameToValue.addAll(localizations[_localeName]);
}
DefaultMaterialLocalizations._(this.locale, this._nameToValue);
/// The locale for which the values of this class's localized resources
/// have been translated.
final Locale locale;
final Map<String, String> _nameToValue;
final Map<String, String> _nameToValue = <String, String>{};
String get _localeName {
final String localeName = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
......
......@@ -383,11 +383,14 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
}
// Combine the Localizations for Widgets with the ones contributed
// by the localizationsDelegates parameter, if any.
// by the localizationsDelegates parameter, if any. Only the first delegate
// of a particular LocalizationsDelegate.type is loaded so the
// localizationsDelegate parameter can be used to override
// _WidgetsLocalizationsDelegate.
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
yield const _WidgetsLocalizationsDelegate(); // TODO(ianh): make this configurable
if (widget.localizationsDelegates != null)
yield* widget.localizationsDelegates;
yield const _WidgetsLocalizationsDelegate();
}
@override
......
......@@ -38,10 +38,20 @@ class _Pending {
// This is more complicated than just applying Future.wait to input
// because some of the input.values may be SynchronousFutures. We don't want
// to Future.wait for the synchronous futures.
Future<Map<Type, dynamic>> _loadAll(Locale locale, Iterable<LocalizationsDelegate<dynamic>> delegates) {
Future<Map<Type, dynamic>> _loadAll(Locale locale, Iterable<LocalizationsDelegate<dynamic>> allDelegates) {
final Map<Type, dynamic> output = <Type, dynamic>{};
List<_Pending> pendingList;
// Only load the first delegate for each delgate type.
final Set<Type> types = new Set<Type>();
final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[];
for (LocalizationsDelegate<dynamic> delegate in allDelegates) {
if (!types.contains(delegate.type)) {
types.add(delegate.type);
delegates.add(delegate);
}
}
for (LocalizationsDelegate<dynamic> delegate in delegates) {
final Future<dynamic> inputValue = delegate.load(locale);
dynamic completedValue;
......@@ -227,6 +237,9 @@ class _LocalizationsScope extends InheritedWidget {
/// class _MyDelegate extends LocalizationsDelegate<MyLocalizations> {
/// @override
/// Future<MyLocalizations> load(Locale locale) => MyLocalizations.load(locale);
///
/// @override
/// bool shouldReload(MyLocalizationsDelegate old) => false;
///}
/// ```
///
......@@ -298,8 +311,7 @@ class _LocalizationsScope extends InheritedWidget {
/// One could choose another approach for loading localized resources and looking them up while
/// still conforming to the structure of this example.
class Localizations extends StatefulWidget {
/// Create a widget from which ambient localizations (translated strings)
/// can be obtained.
/// Create a widget from which localizations (like translated strings) can be obtained.
Localizations({
Key key,
@required this.locale,
......@@ -311,6 +323,51 @@ class Localizations extends StatefulWidget {
assert(delegates.any((LocalizationsDelegate<dynamic> delegate) => delegate is LocalizationsDelegate<WidgetsLocalizations>));
}
/// Overrides the inherited [Locale] or [LocalizationsDelegate]s for `child`.
///
/// This factory constructor is used for the (usually rare) situtation where part
/// of an app should be localized for a different locale than the one defined
/// for the device, or if its localizations should come from a different list
/// of [LocalizationsDelegate]s than the list defined by
/// [WidgetsApp.localizationsDelegates].
///
/// For example you could specify that `myWidget` was only to be localized for
/// the US English locale:
///
/// ```dart
/// Widget build(BuildContext context) {
/// return new Localizations.override(
/// context: context,
/// locale: const Locale('en', 'US'),
/// child: myWidget,
/// );
/// }
/// ```
///
/// The `locale` and `delegates` parameters default to the [Localizations.locale]
/// and [Localizations.delegates] values from the nearest [Localizations] ancestor.
///
/// To override the [Localizations.locale] or [Localizations.delegates] for an
/// entire app, specify [WidgetsApp.locale] or [WidgetsApp.localizationsDelegates]
/// (or specify the same parameters for [MaterialApp]).
factory Localizations.override({
Key key,
@required BuildContext context,
Locale locale,
List<LocalizationsDelegate<dynamic>> delegates,
Widget child,
}) {
final List<LocalizationsDelegate<dynamic>> mergedDelegates = Localizations._delegatesOf(context);
if (delegates != null)
mergedDelegates.insertAll(0, delegates);
return new Localizations(
key: key,
locale: locale ?? Localizations.localeOf(context),
delegates: mergedDelegates,
child: child,
);
}
/// The resources returned by [Localizations.of] will be specific to this locale.
final Locale locale;
......@@ -326,9 +383,19 @@ class Localizations extends StatefulWidget {
static Locale localeOf(BuildContext context) {
assert(context != null);
final _LocalizationsScope scope = context.inheritFromWidgetOfExactType(_LocalizationsScope);
assert(scope != null, 'a Localizations ancestor was not found');
return scope.localizationsState.locale;
}
// There doesn't appear to be a need to make this public. See the
// Localizations.override factory constructor.
static List<LocalizationsDelegate<dynamic>> _delegatesOf(BuildContext context) {
assert(context != null);
final _LocalizationsScope scope = context.inheritFromWidgetOfExactType(_LocalizationsScope);
assert(scope != null, 'a Localizations ancestor was not found');
return new List<LocalizationsDelegate<dynamic>>.from(scope.localizationsState.widget.delegates);
}
/// Returns the 'type' localized resources for the widget tree that
/// corresponds to [BuildContext] `context`.
///
......@@ -345,6 +412,7 @@ class Localizations extends StatefulWidget {
assert(context != null);
assert(type != null);
final _LocalizationsScope scope = context.inheritFromWidgetOfExactType(_LocalizationsScope);
assert(scope != null, 'a Localizations ancestor was not found');
return scope.localizationsState.resourcesFor<T>(type);
}
......
......@@ -3,11 +3,33 @@
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
class FooMaterialLocalizations extends DefaultMaterialLocalizations {
FooMaterialLocalizations(Locale locale) : super(locale);
@override
String get backButtonTooltip => 'foo';
}
class FooMaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
const FooMaterialLocalizationsDelegate();
@override
Future<FooMaterialLocalizations> load(Locale locale) {
return new SynchronousFuture<FooMaterialLocalizations>(new FooMaterialLocalizations(locale));
}
@override
bool shouldReload(FooMaterialLocalizationsDelegate old) => false;
}
Widget buildFrame({
Locale locale,
Iterable<LocalizationsDelegate<dynamic>> delegates,
WidgetBuilder buildContent,
LocaleResolutionCallback localeResolutionCallback,
Iterable<Locale> supportedLocales: const <Locale>[
const Locale('en', 'US'),
const Locale('es', 'es'),
......@@ -16,6 +38,8 @@ Widget buildFrame({
return new MaterialApp(
color: const Color(0xFFFFFFFF),
locale: locale,
localizationsDelegates: delegates,
localeResolutionCallback: localeResolutionCallback,
supportedLocales: supportedLocales,
onGenerateRoute: (RouteSettings settings) {
return new MaterialPageRoute<Null>(
......@@ -133,6 +157,101 @@ void main() {
expect(localizations.selectedRowCountTitle(123456789), '123.456.789 artículos seleccionados');
});
testWidgets('Localizations.override widget tracks parent\'s locale', (WidgetTester tester) async {
Widget buildLocaleFrame(Locale locale) {
return buildFrame(
locale: locale,
buildContent: (BuildContext context) {
return new Localizations.override(
context: context,
child: new Builder(
builder: (BuildContext context) {
// No MaterialLocalizations are defined for the first Localizations
// ancestor, so we should get the values from the default one, i.e.
// the one created by WidgetsApp via the LocalizationsDelegate
// provided by MaterialApp.
return new Text(MaterialLocalizations.of(context).backButtonTooltip);
},
),
);
}
);
}
await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US')));
expect(find.text('Back'), findsOneWidget);
await tester.pumpWidget(buildLocaleFrame(const Locale('de', 'DE')));
expect(find.text('Zurück'), findsOneWidget);
await tester.pumpWidget(buildLocaleFrame(const Locale('zh', 'CN')));
expect(find.text('返回'), findsOneWidget);
});
testWidgets('Localizations.override widget with hardwired locale', (WidgetTester tester) async {
Widget buildLocaleFrame(Locale locale) {
return buildFrame(
locale: locale,
buildContent: (BuildContext context) {
return new Localizations.override(
context: context,
locale: const Locale('en', 'US'),
child: new Builder(
builder: (BuildContext context) {
// No MaterialLocalizations are defined for the Localizations.override
// ancestor, so we should get all values from the default one, i.e.
// the one created by WidgetsApp via the LocalizationsDelegate
// provided by MaterialApp.
return new Text(MaterialLocalizations.of(context).backButtonTooltip);
},
),
);
}
);
}
await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US')));
expect(find.text('Back'), findsOneWidget);
await tester.pumpWidget(buildLocaleFrame(const Locale('de', 'DE')));
expect(find.text('Back'), findsOneWidget);
await tester.pumpWidget(buildLocaleFrame(const Locale('zh', 'CN')));
expect(find.text('Back'), findsOneWidget);
});
testWidgets('MaterialApp overrides MaterialLocalizations', (WidgetTester tester) async {
final Key textKey = new UniqueKey();
await tester.pumpWidget(
buildFrame(
// Accept whatever locale we're given
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
delegates: <FooMaterialLocalizationsDelegate>[
const FooMaterialLocalizationsDelegate(),
],
buildContent: (BuildContext context) {
// Should always be 'foo', no matter what the locale is
return new Text(
MaterialLocalizations.of(context).backButtonTooltip,
key: textKey,
);
}
)
);
expect(tester.widget<Text>(find.byKey(textKey)).data, 'foo');
await tester.binding.setLocale('zh', 'CN');
await tester.pump();
expect(find.text('foo'), findsOneWidget);
await tester.binding.setLocale('de', 'DE');
await tester.pump();
expect(find.text('foo'), findsOneWidget);
});
testWidgets('deprecated Android/Java locales are modernized', (WidgetTester tester) async {
final Key textKey = new UniqueKey();
......
......@@ -103,6 +103,36 @@ class AsyncMoreLocalizationsDelegate extends LocalizationsDelegate<MoreLocalizat
bool shouldReload(AsyncMoreLocalizationsDelegate old) => false;
}
// Same as _WidgetsLocalizationsDelegate in widgets/app.dart
class DefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const DefaultWidgetsLocalizationsDelegate();
@override
Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
@override
bool shouldReload(DefaultWidgetsLocalizationsDelegate old) => false;
}
class OnlyRTLDefaultWidgetsLocalizations extends DefaultWidgetsLocalizations {
OnlyRTLDefaultWidgetsLocalizations(Locale locale) : super(locale);
@override
TextDirection get textDirection => TextDirection.rtl;
}
class OnlyRTLDefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const OnlyRTLDefaultWidgetsLocalizationsDelegate();
@override
Future<WidgetsLocalizations> load(Locale locale) {
return new SynchronousFuture<WidgetsLocalizations>(new OnlyRTLDefaultWidgetsLocalizations(locale));
}
@override
bool shouldReload(OnlyRTLDefaultWidgetsLocalizationsDelegate old) => false;
}
Widget buildFrame({
Locale locale,
Iterable<LocalizationsDelegate<dynamic>> delegates,
......@@ -504,15 +534,116 @@ void main() {
await tester.pumpAndSettle();
expect(find.text('zh_CN'), findsOneWidget);
});
}
// Same as _WidgetsLocalizationsDelegate in widgets/app.dart
class DefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const DefaultWidgetsLocalizationsDelegate();
testWidgets('Localizations.override widget tracks parent\'s locale and delegates', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
// Accept whatever locale we're given
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
buildContent: (BuildContext context) {
return new Localizations.override(
context: context,
child: new Builder(
builder: (BuildContext context) {
final Locale locale = Localizations.localeOf(context);
final TextDirection direction = WidgetsLocalizations.of(context).textDirection;
return new Text('$locale $direction');
},
),
);
}
)
);
@override
Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
// Initial WidgetTester locale is new Locale('', '')
await tester.pumpAndSettle();
expect(find.text('_ TextDirection.ltr'), findsOneWidget);
await tester.binding.setLocale('en', 'CA');
await tester.pumpAndSettle();
expect(find.text('en_CA TextDirection.ltr'), findsOneWidget);
await tester.binding.setLocale('ar', 'EG');
await tester.pumpAndSettle();
expect(find.text('ar_EG TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('da', 'DA');
await tester.pumpAndSettle();
expect(find.text('da_DA TextDirection.ltr'), findsOneWidget);
});
testWidgets('Localizations.override widget overrides parent\'s DefaultWidgetLocalizations', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
// Accept whatever locale we're given
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
buildContent: (BuildContext context) {
return new Localizations.override(
context: context,
delegates: <OnlyRTLDefaultWidgetsLocalizationsDelegate>[
// Override: no matter what the locale, textDirection is always RTL.
const OnlyRTLDefaultWidgetsLocalizationsDelegate(),
],
child: new Builder(
builder: (BuildContext context) {
final Locale locale = Localizations.localeOf(context);
final TextDirection direction = WidgetsLocalizations.of(context).textDirection;
return new Text('$locale $direction');
},
),
);
}
)
);
// Initial WidgetTester locale is new Locale('', '')
await tester.pumpAndSettle();
expect(find.text('_ TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('en', 'CA');
await tester.pumpAndSettle();
expect(find.text('en_CA TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('ar', 'EG');
await tester.pumpAndSettle();
expect(find.text('ar_EG TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('da', 'DA');
await tester.pumpAndSettle();
expect(find.text('da_DA TextDirection.rtl'), findsOneWidget);
});
testWidgets('WidgetsApp overrides DefaultWidgetLocalizations', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
// Accept whatever locale we're given
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
delegates: <OnlyRTLDefaultWidgetsLocalizationsDelegate>[
const OnlyRTLDefaultWidgetsLocalizationsDelegate(),
],
buildContent: (BuildContext context) {
final Locale locale = Localizations.localeOf(context);
final TextDirection direction = WidgetsLocalizations.of(context).textDirection;
return new Text('$locale $direction');
}
)
);
// Initial WidgetTester locale is new Locale('', '')
await tester.pumpAndSettle();
expect(find.text('_ TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('en', 'CA');
await tester.pumpAndSettle();
expect(find.text('en_CA TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('ar', 'EG');
await tester.pumpAndSettle();
expect(find.text('ar_EG TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('da', 'DA');
await tester.pumpAndSettle();
expect(find.text('da_DA TextDirection.rtl'), findsOneWidget);
});
@override
bool shouldReload(DefaultWidgetsLocalizationsDelegate old) => false;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment