1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:ui' show Locale;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'binding.dart';
import 'container.dart';
import 'framework.dart';
// Examples can assume:
// class Intl { static String message(String s, { String name, String locale }) => ''; }
// Future<void> initializeMessages(String locale) => null;
// Used by loadAll() to record LocalizationsDelegate.load() futures we're
// waiting for.
class _Pending {
_Pending(this.delegate, this.futureValue);
final LocalizationsDelegate<dynamic> delegate;
final Future<dynamic> futureValue;
}
// A utility function used by Localizations to generate one future
// that completes when all of the LocalizationsDelegate.load() futures
// complete. The returned map is indexed by each delegate's type.
//
// The input future values must have distinct types.
//
// The returned Future<Map> will resolve when all of the input map's
// future values have resolved. If all of the input map's values are
// SynchronousFutures then a SynchronousFuture will be returned
// immediately.
//
// 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>> allDelegates) {
final Map<Type, dynamic> output = <Type, dynamic>{};
List<_Pending> pendingList;
// Only load the first delegate for each delegate type that supports
// locale.languageCode.
final Set<Type> types = <Type>{};
final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[];
for (final LocalizationsDelegate<dynamic> delegate in allDelegates) {
if (!types.contains(delegate.type) && delegate.isSupported(locale)) {
types.add(delegate.type);
delegates.add(delegate);
}
}
for (final LocalizationsDelegate<dynamic> delegate in delegates) {
final Future<dynamic> inputValue = delegate.load(locale);
dynamic completedValue;
final Future<dynamic> futureValue = inputValue.then<dynamic>((dynamic value) {
return completedValue = value;
});
if (completedValue != null) { // inputValue was a SynchronousFuture
final Type type = delegate.type;
assert(!output.containsKey(type));
output[type] = completedValue;
} else {
pendingList ??= <_Pending>[];
pendingList.add(_Pending(delegate, futureValue));
}
}
// All of the delegate.load() values were synchronous futures, we're done.
if (pendingList == null)
return SynchronousFuture<Map<Type, dynamic>>(output);
// Some of delegate.load() values were asynchronous futures. Wait for them.
return Future.wait<dynamic>(pendingList.map<Future<dynamic>>((_Pending p) => p.futureValue))
.then<Map<Type, dynamic>>((List<dynamic> values) {
assert(values.length == pendingList.length);
for (int i = 0; i < values.length; i += 1) {
final Type type = pendingList[i].delegate.type;
assert(!output.containsKey(type));
output[type] = values[i];
}
return output;
});
}
/// A factory for a set of localized resources of type `T`, to be loaded by a
/// [Localizations] widget.
///
/// Typical applications have one [Localizations] widget which is created by the
/// [WidgetsApp] and configured with the app's `localizationsDelegates`
/// parameter (a list of delegates). The delegate's [type] is used to identify
/// the object created by an individual delegate's [load] method.
abstract class LocalizationsDelegate<T> {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
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
/// when the resources have finished loading.
///
/// It's assumed that the this method will return an object that contains
/// a collection of related resources (typically defined with one method per
/// resource). The object will be retrieved with [Localizations.of].
Future<T> load(Locale locale);
/// Returns true if the resources for this delegate should be loaded
/// again by calling the [load] method.
///
/// This method is called whenever its [Localizations] widget is
/// rebuilt. If it returns true then dependent widgets will be rebuilt
/// after [load] has completed.
bool shouldReload(covariant LocalizationsDelegate<T> old);
/// The type of the object returned by the [load] method, T by default.
///
/// This type is used to retrieve the object "loaded" by this
/// [LocalizationsDelegate] from the [Localizations] inherited widget.
/// For example the object loaded by `LocalizationsDelegate<Foo>` would
/// be retrieved with:
/// ```dart
/// Foo foo = Localizations.of<Foo>(context, Foo);
/// ```
///
/// It's rarely necessary to override this getter.
Type get type => T;
@override
String toString() => '${objectRuntimeType(this, 'LocalizationsDelegate')}[$type]';
}
/// Interface for localized resource values for the lowest levels of the Flutter
/// framework.
///
/// In particular, this maps locales to a specific [Directionality] using the
/// [textDirection] property.
///
/// See also:
///
/// * [DefaultWidgetsLocalizations], which implements this interface and
/// supports a variety of locales.
abstract class WidgetsLocalizations {
/// The reading direction for text in this locale.
TextDirection get textDirection;
/// The `WidgetsLocalizations` from the closest [Localizations] instance
/// that encloses the given context.
///
/// This method is just a convenient shorthand for:
/// `Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations)`.
///
/// References to the localized resources defined by this class are typically
/// written in terms of this method. For example:
///
/// ```dart
/// textDirection: WidgetsLocalizations.of(context).textDirection,
/// ```
static WidgetsLocalizations of(BuildContext context) {
return Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations);
}
}
class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
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
Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
@override
bool shouldReload(_WidgetsLocalizationsDelegate old) => false;
@override
String toString() => 'DefaultWidgetsLocalizations.delegate(en_US)';
}
/// US English localizations for the widgets library.
///
/// See also:
///
/// * [GlobalWidgetsLocalizations], which provides widgets localizations for
/// many languages.
/// * [WidgetsApp.delegates], which automatically includes
/// [DefaultWidgetsLocalizations.delegate] by default.
class DefaultWidgetsLocalizations implements WidgetsLocalizations {
/// Construct an object that defines the localized values for the widgets
/// library for US English (only).
///
/// [LocalizationsDelegate] implementations typically call the static [load]
const DefaultWidgetsLocalizations();
@override
TextDirection get textDirection => TextDirection.ltr;
/// Creates an object that provides US English resource values for the
/// lowest levels of the widgets library.
///
/// The [locale] parameter is ignored.
///
/// This method is typically used to create a [LocalizationsDelegate].
/// The [WidgetsApp] does so by default.
static Future<WidgetsLocalizations> load(Locale locale) {
return SynchronousFuture<WidgetsLocalizations>(const DefaultWidgetsLocalizations());
}
/// A [LocalizationsDelegate] that uses [DefaultWidgetsLocalizations.load]
/// to create an instance of this class.
///
/// [WidgetsApp] automatically adds this value to [WidgetApp.localizationsDelegates].
static const LocalizationsDelegate<WidgetsLocalizations> delegate = _WidgetsLocalizationsDelegate();
}
class _LocalizationsScope extends InheritedWidget {
const _LocalizationsScope({
Key key,
@required this.locale,
@required this.localizationsState,
@required this.typeToResources,
Widget child,
}) : assert(localizationsState != null),
assert(typeToResources != null),
super(key: key, child: child);
final Locale locale;
final _LocalizationsState localizationsState;
final Map<Type, dynamic> typeToResources;
@override
bool updateShouldNotify(_LocalizationsScope old) {
return typeToResources != old.typeToResources;
}
}
/// Defines the [Locale] for its `child` and the localized resources that the
/// child depends on.
///
/// Localized resources are loaded by the list of [LocalizationsDelegate]
/// `delegates`. Each delegate is essentially a factory for a collection
/// of localized resources. There are multiple delegates because there are
/// multiple sources for localizations within an app.
///
/// Delegates are typically simple subclasses of [LocalizationsDelegate] that
/// override [LocalizationsDelegate.load]. For example a delegate for the
/// `MyLocalizations` class defined below would be:
///
/// ```dart
/// class _MyDelegate extends LocalizationsDelegate<MyLocalizations> {
/// @override
/// Future<MyLocalizations> load(Locale locale) => MyLocalizations.load(locale);
///
/// @override
/// bool shouldReload(MyLocalizationsDelegate old) => false;
/// }
/// ```
///
/// Each delegate can be viewed as a factory for objects that encapsulate a
/// a set of localized resources. These objects are retrieved with
/// by runtime type with [Localizations.of].
///
/// The [WidgetsApp] class creates a `Localizations` widget so most apps
/// will not need to create one. The widget app's `Localizations` delegates can
/// be initialized with [WidgetsApp.localizationsDelegates]. The [MaterialApp]
/// class also provides a `localizationsDelegates` parameter that's just
/// passed along to the [WidgetsApp].
///
/// Apps should retrieve collections of localized resources with
/// `Localizations.of<MyLocalizations>(context, MyLocalizations)`,
/// where MyLocalizations is an app specific class defines one function per
/// resource. This is conventionally done by a static `.of` method on the
/// MyLocalizations class.
///
/// For example, using the `MyLocalizations` class defined below, one would
/// lookup a localized title string like this:
/// ```dart
/// MyLocalizations.of(context).title()
/// ```
/// If `Localizations` were to be rebuilt with a new `locale` then
/// the widget subtree that corresponds to [BuildContext] `context` would
/// be rebuilt after the corresponding resources had been loaded.
///
/// This class is effectively an [InheritedWidget]. If it's rebuilt with
/// a new `locale` or a different list of delegates or any of its
/// delegates' [LocalizationsDelegate.shouldReload()] methods returns true,
/// then widgets that have created a dependency by calling
/// `Localizations.of(context)` will be rebuilt after the resources
/// for the new locale have been loaded.
///
/// {@tool snippet}
///
/// This following class is defined in terms of the
/// [Dart `intl` package](https://github.com/dart-lang/intl). Using the `intl`
/// package isn't required.
///
/// ```dart
/// class MyLocalizations {
/// MyLocalizations(this.locale);
///
/// final Locale locale;
///
/// static Future<MyLocalizations> load(Locale locale) {
/// return initializeMessages(locale.toString())
/// .then((void _) {
/// return MyLocalizations(locale);
/// });
/// }
///
/// static MyLocalizations of(BuildContext context) {
/// return Localizations.of<MyLocalizations>(context, MyLocalizations);
/// }
///
/// String title() => Intl.message('<title>', name: 'title', locale: locale.toString());
/// // ... more Intl.message() methods like title()
/// }
/// ```
/// {@end-tool}
/// A class based on the `intl` package imports a generated message catalog that provides
/// the `initializeMessages()` function and the per-locale backing store for `Intl.message()`.
/// The message catalog is produced by an `intl` tool that analyzes the source code for
/// classes that contain `Intl.message()` calls. In this case that would just be the
/// `MyLocalizations` class.
///
/// 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 localizations (like translated strings) can be obtained.
Localizations({
Key key,
@required this.locale,
@required this.delegates,
this.child,
}) : assert(locale != null),
assert(delegates != null),
assert(delegates.any((LocalizationsDelegate<dynamic> delegate) => delegate is LocalizationsDelegate<WidgetsLocalizations>)),
super(key: key);
/// Overrides the inherited [Locale] or [LocalizationsDelegate]s for `child`.
///
/// This factory constructor is used for the (usually rare) situation 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 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 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;
/// This list collectively defines the localized resources objects that can
/// be retrieved with [Localizations.of].
final List<LocalizationsDelegate<dynamic>> delegates;
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.child}
final Widget child;
/// The locale of the Localizations widget for the widget tree that
/// corresponds to [BuildContext] `context`.
///
/// If no [Localizations] widget is in scope then the [Localizations.localeOf]
/// method will throw an exception, unless the `nullOk` argument is set to
/// true, in which case it returns null.
static Locale localeOf(BuildContext context, { bool nullOk = false }) {
assert(context != null);
assert(nullOk != null);
final _LocalizationsScope scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
if (nullOk && scope == null)
return null;
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.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
assert(scope != null, 'a Localizations ancestor was not found');
return List<LocalizationsDelegate<dynamic>>.from(scope.localizationsState.widget.delegates);
}
/// Returns the localized resources object of the given `type` for the widget
/// tree that corresponds to the given `context`.
///
/// Returns null if no resources object of the given `type` exists within
/// the given `context`.
///
/// This method is typically used by a static factory method on the `type`
/// class. For example Flutter's MaterialLocalizations class looks up Material
/// resources with a method defined like this:
///
/// ```dart
/// static MaterialLocalizations of(BuildContext context) {
/// return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
/// }
/// ```
static T of<T>(BuildContext context, Type type) {
assert(context != null);
assert(type != null);
final _LocalizationsScope scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
return scope?.localizationsState?.resourcesFor<T>(type);
}
@override
_LocalizationsState createState() => _LocalizationsState();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<Locale>('locale', locale));
properties.add(IterableProperty<LocalizationsDelegate<dynamic>>('delegates', delegates));
}
}
class _LocalizationsState extends State<Localizations> {
final GlobalKey _localizedResourcesScopeKey = GlobalKey();
Map<Type, dynamic> _typeToResources = <Type, dynamic>{};
Locale get locale => _locale;
Locale _locale;
@override
void initState() {
super.initState();
load(widget.locale);
}
bool _anyDelegatesShouldReload(Localizations old) {
if (widget.delegates.length != old.delegates.length)
return true;
final List<LocalizationsDelegate<dynamic>> delegates = widget.delegates.toList();
final List<LocalizationsDelegate<dynamic>> oldDelegates = old.delegates.toList();
for (int i = 0; i < delegates.length; i += 1) {
final LocalizationsDelegate<dynamic> delegate = delegates[i];
final LocalizationsDelegate<dynamic> oldDelegate = oldDelegates[i];
if (delegate.runtimeType != oldDelegate.runtimeType || delegate.shouldReload(oldDelegate))
return true;
}
return false;
}
@override
void didUpdateWidget(Localizations old) {
super.didUpdateWidget(old);
if (widget.locale != old.locale
|| (widget.delegates == null && old.delegates != null)
|| (widget.delegates != null && old.delegates == null)
|| (widget.delegates != null && _anyDelegatesShouldReload(old)))
load(widget.locale);
}
void load(Locale locale) {
final Iterable<LocalizationsDelegate<dynamic>> delegates = widget.delegates;
if (delegates == null || delegates.isEmpty) {
_locale = locale;
return;
}
Map<Type, dynamic> typeToResources;
final Future<Map<Type, dynamic>> typeToResourcesFuture = _loadAll(locale, delegates)
.then<Map<Type, dynamic>>((Map<Type, dynamic> value) {
return typeToResources = value;
});
if (typeToResources != null) {
// All of the delegates' resources loaded synchronously.
_typeToResources = typeToResources;
_locale = locale;
} else {
// - Don't rebuild the dependent widgets until the resources for the new locale
// have finished loading. Until then the old locale will continue to be used.
// - If we're running at app startup time then defer reporting the first
// "useful" frame until after the async load has completed.
RendererBinding.instance.deferFirstFrame();
typeToResourcesFuture.then<void>((Map<Type, dynamic> value) {
if (mounted) {
setState(() {
_typeToResources = value;
_locale = locale;
});
}
RendererBinding.instance.allowFirstFrame();
});
}
}
T resourcesFor<T>(Type type) {
assert(type != null);
final T resources = _typeToResources[type] as T;
return resources;
}
TextDirection get _textDirection {
final WidgetsLocalizations resources = _typeToResources[WidgetsLocalizations] as WidgetsLocalizations;
assert(resources != null);
return resources.textDirection;
}
@override
Widget build(BuildContext context) {
if (_locale == null)
return Container();
return Semantics(
textDirection: _textDirection,
child: _LocalizationsScope(
key: _localizedResourcesScopeKey,
locale: _locale,
localizationsState: this,
typeToResources: _typeToResources,
child: Directionality(
textDirection: _textDirection,
child: widget.child,
),
),
);
}
}