Commit 796c5439 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Language-specific LocalizationDelegates (#12645)

parent a554401e
...@@ -26,6 +26,9 @@ class _StocksLocalizationsDelegate extends LocalizationsDelegate<StockStrings> { ...@@ -26,6 +26,9 @@ class _StocksLocalizationsDelegate extends LocalizationsDelegate<StockStrings> {
@override @override
Future<StockStrings> load(Locale locale) => StockStrings.load(locale); Future<StockStrings> load(Locale locale) => StockStrings.load(locale);
@override
bool isSupported(Locale locale) => locale.languageCode == 'es' || locale.languageCode == 'en';
@override @override
bool shouldReload(_StocksLocalizationsDelegate old) => false; bool shouldReload(_StocksLocalizationsDelegate old) => false;
} }
......
...@@ -187,6 +187,9 @@ abstract class MaterialLocalizations { ...@@ -187,6 +187,9 @@ abstract class MaterialLocalizations {
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> { class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
const _MaterialLocalizationsDelegate(); const _MaterialLocalizationsDelegate();
@override
bool isSupported(Locale locale) => locale.languageCode == 'en';
@override @override
Future<MaterialLocalizations> load(Locale locale) => DefaultMaterialLocalizations.load(locale); Future<MaterialLocalizations> load(Locale locale) => DefaultMaterialLocalizations.load(locale);
......
...@@ -42,11 +42,12 @@ Future<Map<Type, dynamic>> _loadAll(Locale locale, Iterable<LocalizationsDelegat ...@@ -42,11 +42,12 @@ Future<Map<Type, dynamic>> _loadAll(Locale locale, Iterable<LocalizationsDelegat
final Map<Type, dynamic> output = <Type, dynamic>{}; final Map<Type, dynamic> output = <Type, dynamic>{};
List<_Pending> pendingList; List<_Pending> pendingList;
// Only load the first delegate for each delgate type. // Only load the first delegate for each delegate type that supports
// locale.languageCode.
final Set<Type> types = new Set<Type>(); final Set<Type> types = new Set<Type>();
final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[]; final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[];
for (LocalizationsDelegate<dynamic> delegate in allDelegates) { for (LocalizationsDelegate<dynamic> delegate in allDelegates) {
if (!types.contains(delegate.type)) { if (!types.contains(delegate.type) && delegate.isSupported(locale)) {
types.add(delegate.type); types.add(delegate.type);
delegates.add(delegate); delegates.add(delegate);
} }
...@@ -97,6 +98,12 @@ abstract class LocalizationsDelegate<T> { ...@@ -97,6 +98,12 @@ abstract class LocalizationsDelegate<T> {
/// const constructors so that they can be used in const expressions. /// const constructors so that they can be used in const expressions.
const LocalizationsDelegate(); const LocalizationsDelegate();
/// Whether resources for the given locale can be loaded by this delegate.
///
/// Return true if the instance of `T` loaded by this delegate's [load]
/// method supports the given `locale`'s language.
bool isSupported(Locale locale);
/// Start loading the resources for `locale`. The returned future completes /// Start loading the resources for `locale`. The returned future completes
/// when the resources have finished loading. /// when the resources have finished loading.
/// ///
...@@ -164,6 +171,11 @@ abstract class WidgetsLocalizations { ...@@ -164,6 +171,11 @@ abstract class WidgetsLocalizations {
class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> { class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const _WidgetsLocalizationsDelegate(); const _WidgetsLocalizationsDelegate();
// This is convenient simplification. It would be more correct test if the locale's
// text-direction is LTR.
@override
bool isSupported(Locale locale) => true;
@override @override
Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale); Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
......
...@@ -28,6 +28,9 @@ class MockClipboard { ...@@ -28,6 +28,9 @@ class MockClipboard {
} }
class MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> { class MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
@override
bool isSupported(Locale locale) => true;
@override @override
Future<MaterialLocalizations> load(Locale locale) => DefaultMaterialLocalizations.load(locale); Future<MaterialLocalizations> load(Locale locale) => DefaultMaterialLocalizations.load(locale);
...@@ -36,6 +39,9 @@ class MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocali ...@@ -36,6 +39,9 @@ class MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocali
} }
class WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> { class WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
@override
bool isSupported(Locale locale) => true;
@override @override
Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale); Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
......
...@@ -423,6 +423,27 @@ void _loadDateIntlDataIfNotLoaded() { ...@@ -423,6 +423,27 @@ void _loadDateIntlDataIfNotLoaded() {
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> { class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
const _MaterialLocalizationsDelegate(); const _MaterialLocalizationsDelegate();
static const List<String> _supportedLanguages = const <String>[
'ar', // Arabic
'de', // German
'en', // English
'es', // Spanish
'fa', // Farsi
'fr', // French
'he', // Hebrew
'it', // Italian
'ja', // Japanese
'ps', // Pashto
'pt', // Portugese
'ru', // Russian
'sd', // Sindhi
'ur', // Urdu
'zh', // Simplified Chinese
];
@override
bool isSupported(Locale locale) => _supportedLanguages.contains(locale.languageCode);
@override @override
Future<MaterialLocalizations> load(Locale locale) => GlobalMaterialLocalizations.load(locale); Future<MaterialLocalizations> load(Locale locale) => GlobalMaterialLocalizations.load(locale);
......
...@@ -67,6 +67,9 @@ class GlobalWidgetsLocalizations implements WidgetsLocalizations { ...@@ -67,6 +67,9 @@ class GlobalWidgetsLocalizations implements WidgetsLocalizations {
class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> { class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const _WidgetsLocalizationsDelegate(); const _WidgetsLocalizationsDelegate();
@override
bool isSupported(Locale locale) => true;
@override @override
Future<WidgetsLocalizations> load(Locale locale) => GlobalWidgetsLocalizations.load(locale); Future<WidgetsLocalizations> load(Locale locale) => GlobalWidgetsLocalizations.load(locale);
......
...@@ -55,6 +55,9 @@ class _DummyLocalizationsDelegate extends LocalizationsDelegate<DummyLocalizatio ...@@ -55,6 +55,9 @@ class _DummyLocalizationsDelegate extends LocalizationsDelegate<DummyLocalizatio
@override @override
Future<DummyLocalizations> load(Locale locale) async => new DummyLocalizations(); Future<DummyLocalizations> load(Locale locale) async => new DummyLocalizations();
@override
bool isSupported(Locale locale) => true;
@override @override
bool shouldReload(_DummyLocalizationsDelegate old) => true; bool shouldReload(_DummyLocalizationsDelegate old) => true;
} }
......
...@@ -8,18 +8,29 @@ import 'package:flutter_localizations/flutter_localizations.dart'; ...@@ -8,18 +8,29 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
class FooMaterialLocalizations extends GlobalMaterialLocalizations { class FooMaterialLocalizations extends GlobalMaterialLocalizations {
FooMaterialLocalizations(Locale locale) : super(locale); FooMaterialLocalizations(Locale locale, this.backButtonTooltip) : super(locale);
@override @override
String get backButtonTooltip => 'foo'; final String backButtonTooltip;
} }
class FooMaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> { class FooMaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
const FooMaterialLocalizationsDelegate(); const FooMaterialLocalizationsDelegate({
this.supportedLanguage: 'en',
this.backButtonTooltip: 'foo'
});
final String supportedLanguage;
final String backButtonTooltip;
@override
bool isSupported(Locale locale) {
return supportedLanguage == 'allLanguages' ? true : locale.languageCode == supportedLanguage;
}
@override @override
Future<FooMaterialLocalizations> load(Locale locale) { Future<FooMaterialLocalizations> load(Locale locale) {
return new SynchronousFuture<FooMaterialLocalizations>(new FooMaterialLocalizations(locale)); return new SynchronousFuture<FooMaterialLocalizations>(new FooMaterialLocalizations(locale, backButtonTooltip));
} }
@override @override
...@@ -143,7 +154,42 @@ void main() { ...@@ -143,7 +154,42 @@ void main() {
expect(find.text('Back'), findsOneWidget); expect(find.text('Back'), findsOneWidget);
}); });
testWidgets('MaterialApp overrides MaterialLocalizations', (WidgetTester tester) async { testWidgets('MaterialApp adds MaterialLocalizations for additional languages', (WidgetTester tester) async {
final Key textKey = new UniqueKey();
await tester.pumpWidget(
buildFrame(
delegates: <FooMaterialLocalizationsDelegate>[
const FooMaterialLocalizationsDelegate(supportedLanguage: 'fr', backButtonTooltip: 'FR'),
const FooMaterialLocalizationsDelegate(supportedLanguage: 'de', backButtonTooltip: 'DE'),
],
supportedLocales: const <Locale>[
const Locale('en', ''),
const Locale('fr', ''),
const Locale('de', ''),
],
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, 'Back');
await tester.binding.setLocale('fr', 'CA');
await tester.pump();
expect(find.text('FR'), findsOneWidget);
await tester.binding.setLocale('de', 'DE');
await tester.pump();
expect(find.text('DE'), findsOneWidget);
});
testWidgets('MaterialApp overrides MaterialLocalizations for all locales', (WidgetTester tester) async {
final Key textKey = new UniqueKey(); final Key textKey = new UniqueKey();
await tester.pumpWidget( await tester.pumpWidget(
...@@ -151,7 +197,7 @@ void main() { ...@@ -151,7 +197,7 @@ void main() {
// Accept whatever locale we're given // Accept whatever locale we're given
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale, localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
delegates: <FooMaterialLocalizationsDelegate>[ delegates: <FooMaterialLocalizationsDelegate>[
const FooMaterialLocalizationsDelegate(), const FooMaterialLocalizationsDelegate(supportedLanguage: 'allLanguages'),
], ],
buildContent: (BuildContext context) { buildContent: (BuildContext context) {
// Should always be 'foo', no matter what the locale is // Should always be 'foo', no matter what the locale is
...@@ -174,6 +220,38 @@ void main() { ...@@ -174,6 +220,38 @@ void main() {
expect(find.text('foo'), findsOneWidget); expect(find.text('foo'), findsOneWidget);
}); });
testWidgets('MaterialApp overrides MaterialLocalizations for default locale', (WidgetTester tester) async {
final Key textKey = new UniqueKey();
await tester.pumpWidget(
buildFrame(
delegates: <FooMaterialLocalizationsDelegate>[
const FooMaterialLocalizationsDelegate(supportedLanguage: 'en'),
],
// supportedLocales not specified, so all locales resolve to 'en'
buildContent: (BuildContext context) {
return new Text(
MaterialLocalizations.of(context).backButtonTooltip,
key: textKey,
);
}
)
);
// Unsupported locale '_' (the widget tester's default) resolves to 'en'.
expect(tester.widget<Text>(find.byKey(textKey)).data, 'foo');
// Unsupported locale 'zh' resolves to 'en'.
await tester.binding.setLocale('zh', 'CN');
await tester.pump();
expect(find.text('foo'), findsOneWidget);
// Unsupported locale 'de' resolves to 'en'.
await tester.binding.setLocale('de', 'DE');
await tester.pump();
expect(find.text('foo'), findsOneWidget);
});
testWidgets('deprecated Android/Java locales are modernized', (WidgetTester tester) async { testWidgets('deprecated Android/Java locales are modernized', (WidgetTester tester) async {
final Key textKey = new UniqueKey(); final Key textKey = new UniqueKey();
......
...@@ -35,6 +35,9 @@ class SyncTestLocalizationsDelegate extends LocalizationsDelegate<TestLocalizati ...@@ -35,6 +35,9 @@ class SyncTestLocalizationsDelegate extends LocalizationsDelegate<TestLocalizati
final String prefix; // Changing this value triggers a rebuild final String prefix; // Changing this value triggers a rebuild
final List<bool> shouldReloadValues = <bool>[]; final List<bool> shouldReloadValues = <bool>[];
@override
bool isSupported(Locale locale) => true;
@override @override
Future<TestLocalizations> load(Locale locale) => TestLocalizations.loadSync(locale, prefix); Future<TestLocalizations> load(Locale locale) => TestLocalizations.loadSync(locale, prefix);
...@@ -54,6 +57,9 @@ class AsyncTestLocalizationsDelegate extends LocalizationsDelegate<TestLocalizat ...@@ -54,6 +57,9 @@ class AsyncTestLocalizationsDelegate extends LocalizationsDelegate<TestLocalizat
final String prefix; // Changing this value triggers a rebuild final String prefix; // Changing this value triggers a rebuild
final List<bool> shouldReloadValues = <bool>[]; final List<bool> shouldReloadValues = <bool>[];
@override
bool isSupported(Locale locale) => true;
@override @override
Future<TestLocalizations> load(Locale locale) => TestLocalizations.loadAsync(locale, prefix); Future<TestLocalizations> load(Locale locale) => TestLocalizations.loadAsync(locale, prefix);
...@@ -92,6 +98,9 @@ class SyncMoreLocalizationsDelegate extends LocalizationsDelegate<MoreLocalizati ...@@ -92,6 +98,9 @@ class SyncMoreLocalizationsDelegate extends LocalizationsDelegate<MoreLocalizati
@override @override
Future<MoreLocalizations> load(Locale locale) => MoreLocalizations.loadSync(locale); Future<MoreLocalizations> load(Locale locale) => MoreLocalizations.loadSync(locale);
@override
bool isSupported(Locale locale) => true;
@override @override
bool shouldReload(SyncMoreLocalizationsDelegate old) => false; bool shouldReload(SyncMoreLocalizationsDelegate old) => false;
} }
...@@ -100,6 +109,9 @@ class AsyncMoreLocalizationsDelegate extends LocalizationsDelegate<MoreLocalizat ...@@ -100,6 +109,9 @@ class AsyncMoreLocalizationsDelegate extends LocalizationsDelegate<MoreLocalizat
@override @override
Future<MoreLocalizations> load(Locale locale) => MoreLocalizations.loadAsync(locale); Future<MoreLocalizations> load(Locale locale) => MoreLocalizations.loadAsync(locale);
@override
bool isSupported(Locale locale) => true;
@override @override
bool shouldReload(AsyncMoreLocalizationsDelegate old) => false; bool shouldReload(AsyncMoreLocalizationsDelegate old) => false;
} }
...@@ -112,6 +124,9 @@ class OnlyRTLDefaultWidgetsLocalizations extends DefaultWidgetsLocalizations { ...@@ -112,6 +124,9 @@ class OnlyRTLDefaultWidgetsLocalizations extends DefaultWidgetsLocalizations {
class OnlyRTLDefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> { class OnlyRTLDefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const OnlyRTLDefaultWidgetsLocalizationsDelegate(); const OnlyRTLDefaultWidgetsLocalizationsDelegate();
@override
bool isSupported(Locale locale) => true;
@override @override
Future<WidgetsLocalizations> load(Locale locale) { Future<WidgetsLocalizations> load(Locale locale) {
return new SynchronousFuture<WidgetsLocalizations>(new OnlyRTLDefaultWidgetsLocalizations()); return new SynchronousFuture<WidgetsLocalizations>(new OnlyRTLDefaultWidgetsLocalizations());
......
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