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';
10library;
11
12import 'package:flutter/foundation.dart';
13import 'package:flutter/rendering.dart';
14
15import 'app.dart';
16import 'basic.dart';
17import 'binding.dart';
18import 'debug.dart';
19import 'framework.dart';
20
21// Examples can assume:
22// class Intl { Intl._(); static String message(String s, { String? name, String? locale }) => ''; }
23// Future initializeMessages(String locale) => Future.value();
24// late BuildContext context;
25// class Foo { }
26// const Widget myWidget = Placeholder();
27
28// Used by loadAll() to record LocalizationsDelegate.load() futures we're
29// waiting for.
30class _Pending {
31 _Pending(this.delegate, this.futureValue);
32 final LocalizationsDelegate<dynamic> delegate;
33 final Future<dynamic> futureValue;
34}
35
36// A utility function used by Localizations to generate one future
37// that completes when all of the LocalizationsDelegate.load() futures
38// complete. The returned map is indexed by each delegate's type.
39//
40// The input future values must have distinct types.
41//
42// The returned Future will resolve when all of the input map's
43// future values have resolved. If all of the input map's values are
44// SynchronousFutures then a SynchronousFuture will be returned
45// immediately.
46//
47// This is more complicated than just applying Future.wait to input
48// because some of the input.values may be SynchronousFutures. We don't want
49// to Future.wait for the synchronous futures.
50Future<Map<Type, dynamic>> _loadAll(
51 Locale locale,
52 Iterable<LocalizationsDelegate<dynamic>> allDelegates,
53) {
54 final Map<Type, dynamic> output = <Type, dynamic>{};
55 List<_Pending>? pendingList;
56
57 // Only load the first delegate for each delegate type that supports
58 // locale.languageCode.
59 final Set<Type> types = <Type>{};
60 final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[];
61 for (final LocalizationsDelegate<dynamic> delegate in allDelegates) {
62 if (!types.contains(delegate.type) && delegate.isSupported(locale)) {
63 types.add(delegate.type);
64 delegates.add(delegate);
65 }
66 }
67
68 for (final LocalizationsDelegate<dynamic> delegate in delegates) {
69 final Future<dynamic> inputValue = delegate.load(locale);
70 dynamic completedValue;
71 final Future<dynamic> futureValue = inputValue.then<dynamic>((dynamic value) {
72 return completedValue = value;
73 });
74 if (completedValue != null) {
75 // inputValue was a SynchronousFuture
76 final Type type = delegate.type;
77 assert(!output.containsKey(type));
78 output[type] = completedValue;
79 } else {
80 pendingList ??= <_Pending>[];
81 pendingList.add(_Pending(delegate, futureValue));
82 }
83 }
84
85 // All of the delegate.load() values were synchronous futures, we're done.
86 if (pendingList == null) {
87 return SynchronousFuture<Map<Type, dynamic>>(output);
88 }
89
90 // Some of delegate.load() values were asynchronous futures. Wait for them.
91 return Future.wait<dynamic>(
92 pendingList.map<Future<dynamic>>((_Pending p) => p.futureValue),
93 ).then<Map<Type, dynamic>>((List<dynamic> values) {
94 assert(values.length == pendingList!.length);
95 for (int i = 0; i < values.length; i += 1) {
96 final Type type = pendingList![i].delegate.type;
97 assert(!output.containsKey(type));
98 output[type] = values[i];
99 }
100 return output;
101 });
102}
103
104/// A factory for a set of localized resources of type `T`, to be loaded by a
105/// [Localizations] widget.
106///
107/// Typical applications have one [Localizations] widget which is created by the
108/// [WidgetsApp] and configured with the app's `localizationsDelegates`
109/// parameter (a list of delegates). The delegate's [type] is used to identify
110/// the object created by an individual delegate's [load] method.
111///
112/// An example of a class used as the value of `T` here would be
113/// [MaterialLocalizations].
114abstract class LocalizationsDelegate<T> {
115 /// Abstract const constructor. This constructor enables subclasses to provide
116 /// const constructors so that they can be used in const expressions.
117 const LocalizationsDelegate();
118
119 /// Whether resources for the given locale can be loaded by this delegate.
120 ///
121 /// Return true if the instance of `T` loaded by this delegate's [load]
122 /// method supports the given `locale`'s language.
123 bool isSupported(Locale locale);
124
125 /// Start loading the resources for `locale`. The returned future completes
126 /// when the resources have finished loading.
127 ///
128 /// It's assumed that this method will return an object that contains a
129 /// collection of related string resources (typically defined with one method
130 /// per resource). The object will be retrieved with [Localizations.of].
131 Future<T> load(Locale locale);
132
133 /// Returns true if the resources for this delegate should be loaded
134 /// again by calling the [load] method.
135 ///
136 /// This method is called whenever its [Localizations] widget is
137 /// rebuilt. If it returns true then dependent widgets will be rebuilt
138 /// after [load] has completed.
139 bool shouldReload(covariant LocalizationsDelegate<T> old);
140
141 /// The type of the object returned by the [load] method, T by default.
142 ///
143 /// This type is used to retrieve the object "loaded" by this
144 /// [LocalizationsDelegate] from the [Localizations] inherited widget.
145 /// For example the object loaded by `LocalizationsDelegate<Foo>` would
146 /// be retrieved with:
147 ///
148 /// ```dart
149 /// Foo foo = Localizations.of<Foo>(context, Foo)!;
150 /// ```
151 ///
152 /// It's rarely necessary to override this getter.
153 Type get type => T;
154
155 @override
156 String toString() => '${objectRuntimeType(this, 'LocalizationsDelegate')}[$type]';
157}
158
159/// Interface for localized resource values for the lowest levels of the Flutter
160/// framework.
161///
162/// This class also maps locales to a specific [Directionality] using the
163/// [textDirection] property.
164///
165/// See also:
166///
167/// * [DefaultWidgetsLocalizations], which implements this interface and
168/// supports a variety of locales.
169abstract class WidgetsLocalizations {
170 /// The reading direction for text in this locale.
171 TextDirection get textDirection;
172
173 /// The semantics label used for [SliverReorderableList] to reorder an item in the
174 /// list to the start of the list.
175 String get reorderItemToStart;
176
177 /// The semantics label used for [SliverReorderableList] to reorder an item in the
178 /// list to the end of the list.
179 String get reorderItemToEnd;
180
181 /// The semantics label used for [SliverReorderableList] to reorder an item in the
182 /// list one space up the list.
183 String get reorderItemUp;
184
185 /// The semantics label used for [SliverReorderableList] to reorder an item in the
186 /// list one space down the list.
187 String get reorderItemDown;
188
189 /// The semantics label used for [SliverReorderableList] to reorder an item in the
190 /// list one space left in the list.
191 String get reorderItemLeft;
192
193 /// The semantics label used for [SliverReorderableList] to reorder an item in the
194 /// list one space right in the list.
195 String get reorderItemRight;
196
197 /// Label for "copy" edit buttons and menu items.
198 String get copyButtonLabel;
199
200 /// Label for "cut" edit buttons and menu items.
201 String get cutButtonLabel;
202
203 /// Label for "paste" edit buttons and menu items.
204 String get pasteButtonLabel;
205
206 /// Label for "select all" edit buttons and menu items.
207 String get selectAllButtonLabel;
208
209 /// Label for "look up" edit buttons and menu items.
210 String get lookUpButtonLabel;
211
212 /// Label for "search web" edit buttons and menu items.
213 String get searchWebButtonLabel;
214
215 /// Label for "share" edit buttons and menu items.
216 String get shareButtonLabel;
217
218 /// The `WidgetsLocalizations` from the closest [Localizations] instance
219 /// that encloses the given context.
220 ///
221 /// This method is just a convenient shorthand for:
222 /// `Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations)!`.
223 ///
224 /// References to the localized resources defined by this class are typically
225 /// written in terms of this method. For example:
226 ///
227 /// ```dart
228 /// textDirection: WidgetsLocalizations.of(context).textDirection,
229 /// ```
230 static WidgetsLocalizations of(BuildContext context) {
231 assert(debugCheckHasWidgetsLocalizations(context));
232 return Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations)!;
233 }
234}
235
236class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
237 const _WidgetsLocalizationsDelegate();
238
239 // This is convenient simplification. It would be more correct test if the locale's
240 // text-direction is LTR.
241 @override
242 bool isSupported(Locale locale) => true;
243
244 @override
245 Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
246
247 @override
248 bool shouldReload(_WidgetsLocalizationsDelegate old) => false;
249
250 @override
251 String toString() => 'DefaultWidgetsLocalizations.delegate(en_US)';
252}
253
254/// US English localizations for the widgets library.
255///
256/// See also:
257///
258/// * [GlobalWidgetsLocalizations], which provides widgets localizations for
259/// many languages.
260/// * [WidgetsApp.localizationsDelegates], which automatically includes
261/// [DefaultWidgetsLocalizations.delegate] by default.
262class DefaultWidgetsLocalizations implements WidgetsLocalizations {
263 /// Construct an object that defines the localized values for the widgets
264 /// library for US English (only).
265 ///
266 /// [LocalizationsDelegate] implementations typically call the static [load]
267 const DefaultWidgetsLocalizations();
268
269 @override
270 String get reorderItemUp => 'Move up';
271
272 @override
273 String get reorderItemDown => 'Move down';
274
275 @override
276 String get reorderItemLeft => 'Move left';
277
278 @override
279 String get reorderItemRight => 'Move right';
280
281 @override
282 String get reorderItemToEnd => 'Move to the end';
283
284 @override
285 String get reorderItemToStart => 'Move to the start';
286
287 @override
288 String get copyButtonLabel => 'Copy';
289
290 @override
291 String get cutButtonLabel => 'Cut';
292
293 @override
294 String get pasteButtonLabel => 'Paste';
295
296 @override
297 String get selectAllButtonLabel => 'Select all';
298
299 @override
300 String get lookUpButtonLabel => 'Look Up';
301
302 @override
303 String get searchWebButtonLabel => 'Search Web';
304
305 @override
306 String get shareButtonLabel => 'Share';
307
308 @override
309 TextDirection get textDirection => TextDirection.ltr;
310
311 /// Creates an object that provides US English resource values for the
312 /// lowest levels of the widgets library.
313 ///
314 /// The [locale] parameter is ignored.
315 ///
316 /// This method is typically used to create a [LocalizationsDelegate].
317 /// The [WidgetsApp] does so by default.
318 static Future<WidgetsLocalizations> load(Locale locale) {
319 return SynchronousFuture<WidgetsLocalizations>(const DefaultWidgetsLocalizations());
320 }
321
322 /// A [LocalizationsDelegate] that uses [DefaultWidgetsLocalizations.load]
323 /// to create an instance of this class.
324 ///
325 /// [WidgetsApp] automatically adds this value to [WidgetsApp.localizationsDelegates].
326 static const LocalizationsDelegate<WidgetsLocalizations> delegate =
327 _WidgetsLocalizationsDelegate();
328}
329
330class _LocalizationsScope extends InheritedWidget {
331 const _LocalizationsScope({
332 super.key,
333 required this.locale,
334 required this.localizationsState,
335 required this.typeToResources,
336 required super.child,
337 });
338
339 final Locale locale;
340 final _LocalizationsState localizationsState;
341 final Map<Type, dynamic> typeToResources;
342
343 @override
344 bool updateShouldNotify(_LocalizationsScope old) {
345 return typeToResources != old.typeToResources;
346 }
347}
348
349/// Defines the [Locale] for its `child` and the localized resources that the
350/// child depends on.
351///
352/// ## Defining localized resources
353///
354/// {@tool snippet}
355///
356/// This following class is defined in terms of the
357/// [Dart `intl` package](https://github.com/dart-lang/intl). Using the `intl`
358/// package isn't required.
359///
360/// ```dart
361/// class MyLocalizations {
362/// MyLocalizations(this.locale);
363///
364/// final Locale locale;
365///
366/// static Future<MyLocalizations> load(Locale locale) {
367/// return initializeMessages(locale.toString())
368/// .then((void _) {
369/// return MyLocalizations(locale);
370/// });
371/// }
372///
373/// static MyLocalizations of(BuildContext context) {
374/// return Localizations.of<MyLocalizations>(context, MyLocalizations)!;
375/// }
376///
377/// String title() => Intl.message('<title>', name: 'title', locale: locale.toString());
378/// // ... more Intl.message() methods like title()
379/// }
380/// ```
381/// {@end-tool}
382/// A class based on the `intl` package imports a generated message catalog that provides
383/// the `initializeMessages()` function and the per-locale backing store for `Intl.message()`.
384/// The message catalog is produced by an `intl` tool that analyzes the source code for
385/// classes that contain `Intl.message()` calls. In this case that would just be the
386/// `MyLocalizations` class.
387///
388/// One could choose another approach for loading localized resources and looking them up while
389/// still conforming to the structure of this example.
390///
391/// ## Loading localized resources
392///
393/// Localized resources are loaded by the list of [LocalizationsDelegate]
394/// `delegates`. Each delegate is essentially a factory for a collection
395/// of localized resources. There are multiple delegates because there are
396/// multiple sources for localizations within an app.
397///
398/// Delegates are typically simple subclasses of [LocalizationsDelegate] that
399/// override [LocalizationsDelegate.load]. For example a delegate for the
400/// `MyLocalizations` class defined above would be:
401///
402/// ```dart
403/// // continuing from previous example...
404/// class _MyDelegate extends LocalizationsDelegate<MyLocalizations> {
405/// @override
406/// Future<MyLocalizations> load(Locale locale) => MyLocalizations.load(locale);
407///
408/// @override
409/// bool isSupported(Locale locale) {
410/// // in a real implementation this would only return true for
411/// // locales that are definitely supported.
412/// return true;
413/// }
414///
415/// @override
416/// bool shouldReload(_MyDelegate old) => false;
417/// }
418/// ```
419///
420/// Each delegate can be viewed as a factory for objects that encapsulate a set
421/// of localized resources. These objects are retrieved with
422/// by runtime type with [Localizations.of].
423///
424/// The [WidgetsApp] class creates a [Localizations] widget so most apps
425/// will not need to create one. The widget app's [Localizations] delegates can
426/// be initialized with [WidgetsApp.localizationsDelegates]. The [MaterialApp]
427/// class also provides a `localizationsDelegates` parameter that's just
428/// passed along to the [WidgetsApp].
429///
430/// ## Obtaining localized resources for use in user interfaces
431///
432/// Apps should retrieve collections of localized resources with
433/// `Localizations.of<MyLocalizations>(context, MyLocalizations)`,
434/// where MyLocalizations is an app specific class defines one function per
435/// resource. This is conventionally done by a static `.of` method on the
436/// custom localized resource class (`MyLocalizations` in the example above).
437///
438/// For example, using the `MyLocalizations` class defined above, one would
439/// lookup a localized title string like this:
440///
441/// ```dart
442/// // continuing from previous example...
443/// MyLocalizations.of(context).title()
444/// ```
445///
446/// If [Localizations] were to be rebuilt with a new `locale` then
447/// the widget subtree that corresponds to [BuildContext] `context` would
448/// be rebuilt after the corresponding resources had been loaded.
449///
450/// This class is effectively an [InheritedWidget]. If it's rebuilt with
451/// a new `locale` or a different list of delegates or any of its
452/// delegates' [LocalizationsDelegate.shouldReload()] methods returns true,
453/// then widgets that have created a dependency by calling
454/// `Localizations.of(context)` will be rebuilt after the resources
455/// for the new locale have been loaded.
456///
457/// The [Localizations] widget also instantiates [Directionality] in order to
458/// support the appropriate [Directionality.textDirection] of the localized
459/// resources.
460class Localizations extends StatefulWidget {
461 /// Create a widget from which localizations (like translated strings) can be obtained.
462 Localizations({
463 super.key,
464 required this.locale,
465 required this.delegates,
466 this.child,
467 this.isApplicationLevel = false,
468 }) : assert(
469 delegates.any(
470 (LocalizationsDelegate<dynamic> delegate) =>
471 delegate is LocalizationsDelegate<WidgetsLocalizations>,
472 ),
473 );
474
475 /// Overrides the inherited [Locale] or [LocalizationsDelegate]s for `child`.
476 ///
477 /// This factory constructor is used for the (usually rare) situation where part
478 /// of an app should be localized for a different locale than the one defined
479 /// for the device, or if its localizations should come from a different list
480 /// of [LocalizationsDelegate]s than the list defined by
481 /// [WidgetsApp.localizationsDelegates].
482 ///
483 /// For example you could specify that `myWidget` was only to be localized for
484 /// the US English locale:
485 ///
486 /// ```dart
487 /// Widget build(BuildContext context) {
488 /// return Localizations.override(
489 /// context: context,
490 /// locale: const Locale('en', 'US'),
491 /// child: myWidget,
492 /// );
493 /// }
494 /// ```
495 ///
496 /// The `locale` and `delegates` parameters default to the [Localizations.locale]
497 /// and [Localizations.delegates] values from the nearest [Localizations] ancestor.
498 ///
499 /// To override the [Localizations.locale] or [Localizations.delegates] for an
500 /// entire app, specify [WidgetsApp.locale] or [WidgetsApp.localizationsDelegates]
501 /// (or specify the same parameters for [MaterialApp]).
502 factory Localizations.override({
503 Key? key,
504 required BuildContext context,
505 Locale? locale,
506 List<LocalizationsDelegate<dynamic>>? delegates,
507 Widget? child,
508 }) {
509 final List<LocalizationsDelegate<dynamic>> mergedDelegates = Localizations._delegatesOf(
510 context,
511 );
512 if (delegates != null) {
513 mergedDelegates.insertAll(0, delegates);
514 }
515 return Localizations(
516 key: key,
517 locale: locale ?? Localizations.localeOf(context),
518 delegates: mergedDelegates,
519 child: child,
520 );
521 }
522
523 /// The resources returned by [Localizations.of] will be specific to this locale.
524 final Locale locale;
525
526 /// This list collectively defines the localized resources objects that can
527 /// be retrieved with [Localizations.of].
528 final List<LocalizationsDelegate<dynamic>> delegates;
529
530 /// The widget below this widget in the tree.
531 ///
532 /// {@macro flutter.widgets.ProxyWidget.child}
533 final Widget? child;
534
535 /// Whether this is the main localizations widget that represents the app's
536 /// locale.
537 final bool isApplicationLevel;
538
539 /// The locale of the Localizations widget for the widget tree that
540 /// corresponds to [BuildContext] `context`.
541 ///
542 /// If no [Localizations] widget is in scope then the [Localizations.localeOf]
543 /// method will throw an exception.
544 static Locale localeOf(BuildContext context) {
545 final _LocalizationsScope? scope = context
546 .dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
547 assert(() {
548 if (scope == null) {
549 throw FlutterError(
550 'Requested the Locale of a context that does not include a Localizations ancestor.\n'
551 'To request the Locale, the context used to retrieve the Localizations widget must '
552 'be that of a widget that is a descendant of a Localizations widget.',
553 );
554 }
555 if (scope.localizationsState.locale == null) {
556 throw FlutterError(
557 'Localizations.localeOf found a Localizations widget that had a unexpected null locale.\n',
558 );
559 }
560 return true;
561 }());
562 return scope!.localizationsState.locale!;
563 }
564
565 /// The locale of the Localizations widget for the widget tree that
566 /// corresponds to [BuildContext] `context`.
567 ///
568 /// If no [Localizations] widget is in scope then this function will return
569 /// null.
570 static Locale? maybeLocaleOf(BuildContext context) {
571 final _LocalizationsScope? scope = context
572 .dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
573 return scope?.localizationsState.locale;
574 }
575
576 // There doesn't appear to be a need to make this public. See the
577 // Localizations.override factory constructor.
578 static List<LocalizationsDelegate<dynamic>> _delegatesOf(BuildContext context) {
579 final _LocalizationsScope? scope = context
580 .dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
581 assert(scope != null, 'a Localizations ancestor was not found');
582 return List<LocalizationsDelegate<dynamic>>.of(scope!.localizationsState.widget.delegates);
583 }
584
585 /// Returns the localized resources object of the given `type` for the widget
586 /// tree that corresponds to the given `context`.
587 ///
588 /// Returns null if no resources object of the given `type` exists within
589 /// the given `context`.
590 ///
591 /// This method is typically used by a static factory method on the `type`
592 /// class. For example Flutter's MaterialLocalizations class looks up Material
593 /// resources with a method defined like this:
594 ///
595 /// ```dart
596 /// static MaterialLocalizations of(BuildContext context) {
597 /// return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations)!;
598 /// }
599 /// ```
600 static T? of<T>(BuildContext context, Type type) {
601 final _LocalizationsScope? scope = context
602 .dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
603 return scope?.localizationsState.resourcesFor<T?>(type);
604 }
605
606 @override
607 State<Localizations> createState() => _LocalizationsState();
608
609 @override
610 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
611 super.debugFillProperties(properties);
612 properties.add(DiagnosticsProperty<Locale>('locale', locale));
613 properties.add(IterableProperty<LocalizationsDelegate<dynamic>>('delegates', delegates));
614 }
615}
616
617class _LocalizationsState extends State<Localizations> {
618 final GlobalKey _localizedResourcesScopeKey = GlobalKey();
619 Map<Type, dynamic> _typeToResources = <Type, dynamic>{};
620
621 Locale? get locale => _locale;
622 Locale? _locale;
623
624 @override
625 void initState() {
626 super.initState();
627 load(widget.locale);
628 }
629
630 bool _anyDelegatesShouldReload(Localizations old) {
631 if (widget.delegates.length != old.delegates.length) {
632 return true;
633 }
634 final List<LocalizationsDelegate<dynamic>> delegates = widget.delegates.toList();
635 final List<LocalizationsDelegate<dynamic>> oldDelegates = old.delegates.toList();
636 for (int i = 0; i < delegates.length; i += 1) {
637 final LocalizationsDelegate<dynamic> delegate = delegates[i];
638 final LocalizationsDelegate<dynamic> oldDelegate = oldDelegates[i];
639 if (delegate.runtimeType != oldDelegate.runtimeType || delegate.shouldReload(oldDelegate)) {
640 return true;
641 }
642 }
643 return false;
644 }
645
646 @override
647 void didUpdateWidget(Localizations old) {
648 super.didUpdateWidget(old);
649 if (widget.locale != old.locale || (_anyDelegatesShouldReload(old))) {
650 load(widget.locale);
651 }
652 }
653
654 void load(Locale locale) {
655 final Iterable<LocalizationsDelegate<dynamic>> delegates = widget.delegates;
656 if (delegates.isEmpty) {
657 _locale = locale;
658 return;
659 }
660
661 Map<Type, dynamic>? typeToResources;
662 final Future<Map<Type, dynamic>> typeToResourcesFuture = _loadAll(locale, delegates)
663 .then<Map<Type, dynamic>>((Map<Type, dynamic> value) {
664 return typeToResources = value;
665 });
666
667 if (typeToResources != null) {
668 // All of the delegates' resources loaded synchronously.
669 _typeToResources = typeToResources!;
670 _locale = locale;
671 } else {
672 // - Don't rebuild the dependent widgets until the resources for the new locale
673 // have finished loading. Until then the old locale will continue to be used.
674 // - If we're running at app startup time then defer reporting the first
675 // "useful" frame until after the async load has completed.
676 RendererBinding.instance.deferFirstFrame();
677 typeToResourcesFuture.then<void>((Map<Type, dynamic> value) {
678 if (mounted) {
679 setState(() {
680 _typeToResources = value;
681 _locale = locale;
682 });
683 }
684 RendererBinding.instance.allowFirstFrame();
685 });
686 }
687 }
688
689 T resourcesFor<T>(Type type) {
690 final T resources = _typeToResources[type] as T;
691 return resources;
692 }
693
694 TextDirection get _textDirection {
695 final WidgetsLocalizations resources =
696 _typeToResources[WidgetsLocalizations] as WidgetsLocalizations;
697 return resources.textDirection;
698 }
699
700 @override
701 Widget build(BuildContext context) {
702 // TODO(chunhtai): notify engine about application locale if this is
703 // application level locale.
704 if (_locale == null) {
705 return const SizedBox.shrink();
706 }
707 return Semantics(
708 // If this is not application level, we need to explicit mark the
709 // semantics subtree with the locale.
710 localeForSubtree: widget.isApplicationLevel ? null : widget.locale,
711 container: !widget.isApplicationLevel,
712 textDirection: _textDirection,
713 child: _LocalizationsScope(
714 key: _localizedResourcesScopeKey,
715 locale: _locale!,
716 localizationsState: this,
717 typeToResources: _typeToResources,
718 child: Directionality(textDirection: _textDirection, child: widget.child!),
719 ),
720 );
721 }
722}
723
724/// A helper class used to manage localization resolution.
725///
726/// See also:
727/// * [WidgetsApp], which utilizes [LocalizationsResolver] to handle locales.
728class LocalizationsResolver extends ChangeNotifier with WidgetsBindingObserver {
729 /// Creates a [LocalizationsResolver] that determines the best-fit locale from the set of
730 /// [supportedLocales].
731 ///
732 /// If provided, locale resolution will attempt to use [locale] as the current locale rather
733 /// than the system locale.
734 ///
735 /// Locale resolution behavior can be overridden by providing [localeListResolutionCallback]
736 /// or [localeResolutionCallback].
737 ///
738 /// The delegates set via [localizationsDelegates] collectively define all of the localized
739 /// resources for a [Localizations] widget.
740 ///
741 /// See also:
742 ///
743 /// * [LocalizationsResolver.localeListResolutionCallback] and
744 /// [LocalizationsResolver.localeResolutionCallback] for more details on locale resolution
745 /// behavior.
746 /// * [LocalizationsDelegate] for more details about providing localized resources to a
747 /// [Localizations] widget.
748 LocalizationsResolver({
749 required Iterable<Locale> supportedLocales,
750 Locale? locale,
751 LocaleListResolutionCallback? localeListResolutionCallback,
752 LocaleResolutionCallback? localeResolutionCallback,
753 Iterable<LocalizationsDelegate<Object?>>? localizationsDelegates,
754 }) : _locale = locale,
755 _localeListResolutionCallback = localeListResolutionCallback,
756 _localeResolutionCallback = localeResolutionCallback,
757 _localizationsDelegates = localizationsDelegates,
758 _supportedLocales = supportedLocales {
759 _resolvedLocale = _resolveLocales(
760 WidgetsBinding.instance.platformDispatcher.locales,
761 supportedLocales,
762 );
763 WidgetsBinding.instance.addObserver(this);
764 }
765
766 @override
767 void dispose() {
768 WidgetsBinding.instance.removeObserver(this);
769 super.dispose();
770 }
771
772 /// Replace one or more of the properties used for localization resolution and re-resolve the
773 /// locale.
774 void update({
775 required Locale? locale,
776 required LocaleListResolutionCallback? localeListResolutionCallback,
777 required LocaleResolutionCallback? localeResolutionCallback,
778 required Iterable<LocalizationsDelegate<Object?>>? localizationsDelegates,
779 required Iterable<Locale> supportedLocales,
780 }) {
781 _locale = locale;
782 _localeListResolutionCallback = localeListResolutionCallback;
783 _localeResolutionCallback = localeResolutionCallback;
784 _localizationsDelegates = localizationsDelegates;
785 _supportedLocales = supportedLocales;
786 }
787
788 /// The currently resolved [Locale] based on the current platform locale and
789 /// the provided set of [supportedLocales].
790 Locale get locale {
791 final Locale appLocale = _locale != null
792 ? _resolveLocales(<Locale>[_locale!], supportedLocales)
793 : _resolvedLocale!;
794 assert(_debugCheckLocalizations(appLocale));
795 return appLocale;
796 }
797
798 /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
799 Iterable<LocalizationsDelegate<Object?>> get localizationsDelegates {
800 // Combine the Localizations for Widgets with the ones contributed
801 // by the localizationsDelegates parameter, if any. Only the first delegate
802 // of a particular LocalizationsDelegate.type is loaded so the
803 // localizationsDelegate parameter can be used to override
804 // WidgetsLocalizations.delegate.
805 return <LocalizationsDelegate<Object?>>[
806 if (_localizationsDelegates != null) ..._localizationsDelegates!,
807 DefaultWidgetsLocalizations.delegate,
808 ];
809 }
810
811 Iterable<LocalizationsDelegate<Object?>>? _localizationsDelegates;
812
813 /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
814 ///
815 /// See also:
816 ///
817 /// * [basicLocaleListResolution], the default locale resolution algorithm.
818 LocaleListResolutionCallback? get localeListResolutionCallback => _localeListResolutionCallback;
819 LocaleListResolutionCallback? _localeListResolutionCallback;
820
821 /// {@macro flutter.widgets.LocaleResolutionCallback}
822 LocaleResolutionCallback? get localeResolutionCallback => _localeResolutionCallback;
823 LocaleResolutionCallback? _localeResolutionCallback;
824
825 /// {@macro flutter.widgets.widgetsApp.supportedLocales}
826 ///
827 /// See also:
828 ///
829 /// * [localeResolutionCallback], an app callback that resolves the app's locale
830 /// when the device's locale changes.
831 /// * [localizationsDelegates], which collectively define all of the localized
832 /// resources used by this app.
833 /// * [basicLocaleListResolution], the default locale resolution algorithm.
834 Iterable<Locale> get supportedLocales => _supportedLocales;
835 Iterable<Locale> _supportedLocales;
836
837 Locale? _locale;
838
839 /// This is the resolved locale, and is one of the supportedLocales.
840 Locale? _resolvedLocale;
841
842 @override
843 void didChangeLocales(List<Locale>? locales) {
844 final Locale newLocale = _resolveLocales(locales, supportedLocales);
845 if (newLocale != _resolvedLocale) {
846 _resolvedLocale = newLocale;
847 notifyListeners();
848 }
849 }
850
851 Locale _resolveLocales(List<Locale>? preferredLocales, Iterable<Locale> supportedLocales) {
852 // Attempt to use localeListResolutionCallback.
853 if (localeListResolutionCallback != null) {
854 final Locale? locale = localeListResolutionCallback!(preferredLocales, supportedLocales);
855 if (locale != null) {
856 return locale;
857 }
858 }
859 // localeListResolutionCallback failed, falling back to localeResolutionCallback.
860 if (localeResolutionCallback != null) {
861 final Locale? locale = localeResolutionCallback!(
862 preferredLocales != null && preferredLocales.isNotEmpty ? preferredLocales.first : null,
863 supportedLocales,
864 );
865 if (locale != null) {
866 return locale;
867 }
868 }
869 // Both callbacks failed, falling back to default algorithm.
870 return basicLocaleListResolution(preferredLocales, supportedLocales);
871 }
872
873 @override
874 String toString() => '$LocalizationsResolver';
875
876 bool _debugCheckLocalizations(Locale locale) {
877 assert(() {
878 final Set<Type> unsupportedTypes = localizationsDelegates
879 .map<Type>((LocalizationsDelegate<dynamic> delegate) => delegate.type)
880 .toSet();
881 for (final LocalizationsDelegate<dynamic> delegate in localizationsDelegates) {
882 if (!unsupportedTypes.contains(delegate.type)) {
883 continue;
884 }
885 if (delegate.isSupported(locale)) {
886 unsupportedTypes.remove(delegate.type);
887 }
888 }
889 if (unsupportedTypes.isEmpty) {
890 return true;
891 }
892
893 FlutterError.reportError(
894 FlutterErrorDetails(
895 exception:
896 "Warning: This application's locale, $locale, is not supported by all of its localization delegates.",
897 library: 'widgets',
898 informationCollector: () => <DiagnosticsNode>[
899 for (final Type unsupportedType in unsupportedTypes)
900 ErrorDescription(
901 '• A $unsupportedType delegate that supports the $locale locale was not found.',
902 ),
903 ErrorSpacer(),
904 if (unsupportedTypes.length == 1 &&
905 unsupportedTypes.single.toString() == 'CupertinoLocalizations')
906 // We previously explicitly avoided checking for this class so it's not uncommon for applications
907 // to have omitted importing the required delegate.
908 ...<DiagnosticsNode>[
909 ErrorHint(
910 'If the application is built using GlobalMaterialLocalizations.delegate, consider using '
911 'GlobalMaterialLocalizations.delegates (plural) instead, as that will automatically declare '
912 'the appropriate Cupertino localizations.',
913 ),
914 ErrorSpacer(),
915 ],
916 ErrorHint(
917 'The declared supported locales for this app are: ${supportedLocales.join(", ")}',
918 ),
919 ErrorSpacer(),
920 ErrorDescription(
921 'See https://flutter.dev/to/internationalization/ for more '
922 "information about configuring an app's locale, supportedLocales, "
923 'and localizationsDelegates parameters.',
924 ),
925 ],
926 ),
927 );
928 return true;
929 }());
930 return true;
931 }
932}
933

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com