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