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