| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:intl/intl.dart' as intl; |
| import 'package:intl/date_symbols.dart' as intl; |
| import 'package:intl/date_symbol_data_custom.dart' as date_symbol_data_custom; |
| import 'l10n/date_localizations.dart' as date_localizations; |
| |
| import 'l10n/localizations.dart'; |
| import 'widgets_localizations.dart'; |
| |
| // Watch out: the supported locales list in the doc comment below must be kept |
| // in sync with the list we test, see test/translations_test.dart, and of course |
| // the actual list of supported locales in _MaterialLocalizationsDelegate. |
| |
| /// Implementation of localized strings for the material widgets using the |
| /// `intl` package for date and time formatting. |
| /// |
| /// ## Supported languages |
| /// |
| /// This class supports locales with the following [Locale.languageCode]s: |
| /// |
| /// {@macro flutter.localizations.languages} |
| /// |
| /// This list is available programatically via [kSupportedLanguages]. |
| /// |
| /// ## Sample code |
| /// |
| /// To include the localizations provided by this class in a [MaterialApp], |
| /// add [GlobalMaterialLocalizations.delegates] to |
| /// [MaterialApp.localizationsDelegates], and specify the locales your |
| /// app supports with [MaterialApp.supportedLocales]: |
| /// |
| /// ```dart |
| /// new MaterialApp( |
| /// localizationsDelegates: GlobalMaterialLocalizations.delegates, |
| /// supportedLocales: [ |
| /// const Locale('en', 'US'), // American English |
| /// const Locale('he', 'IL'), // Israeli Hebrew |
| /// // ... |
| /// ], |
| /// // ... |
| /// ) |
| /// ``` |
| /// |
| /// ## Overriding translations |
| /// |
| /// To create a translation that's similar to an existing language's translation |
| /// but has slightly different strings, subclass the relevant translation |
| /// directly and then create a [LocalizationsDelegate<MaterialLocalizations>] |
| /// subclass to define how to load it. |
| /// |
| /// Avoid subclassing an unrelated language (for example, subclassing |
| /// [MaterialLocalizationEn] and then passing a non-English `localeName` to the |
| /// constructor). Doing so will cause confusion for locale-specific behaviors; |
| /// in particular, translations that use the `localeName` for determining how to |
| /// pluralize will end up doing invalid things. Subclassing an existing |
| /// language's translations is only suitable for making small changes to the |
| /// existing strings. For providing a new language entirely, implement |
| /// [MaterialLocalizations] directly. |
| /// |
| /// See also: |
| /// |
| /// * The Flutter Internationalization Tutorial, |
| /// <https://flutter.io/tutorials/internationalization/>. |
| /// * [DefaultMaterialLocalizations], which only provides US English translations. |
| abstract class GlobalMaterialLocalizations implements MaterialLocalizations { |
| /// Initializes an object that defines the material widgets' localized strings |
| /// for the given `locale`. |
| /// |
| /// The arguments are used for further runtime localization of data, |
| /// specifically for selecting plurals, date and time formatting, and number |
| /// formatting. They correspond to the following values: |
| /// |
| /// 1. The string that would be returned by [Intl.canonicalizedLocale] for |
| /// the locale. |
| /// 2. The [intl.DateFormat] for [formatYear]. |
| /// 3. The [intl.DateFormat] for [formatMediumDate]. |
| /// 4. The [intl.DateFormat] for [formatFullDate]. |
| /// 5. The [intl.DateFormat] for [formatMonthYear]. |
| /// 6. The [NumberFormat] for [formatDecimal] (also used by [formatHour] and |
| /// [formatTimeOfDay] when [timeOfDayFormat] doesn't use [HourFormat.HH]). |
| /// 7. The [NumberFormat] for [formatHour] and the hour part of |
| /// [formatTimeOfDay] when [timeOfDayFormat] uses [HourFormat.HH], and for |
| /// [formatMinute] and the minute part of [formatTimeOfDay]. |
| /// |
| /// The [narrowWeekdays] and [firstDayOfWeekIndex] properties use the values |
| /// from the [intl.DateFormat] used by [formatFullDate]. |
| const GlobalMaterialLocalizations({ |
| @required String localeName, |
| @required intl.DateFormat fullYearFormat, |
| @required intl.DateFormat mediumDateFormat, |
| @required intl.DateFormat longDateFormat, |
| @required intl.DateFormat yearMonthFormat, |
| @required intl.NumberFormat decimalFormat, |
| @required intl.NumberFormat twoDigitZeroPaddedFormat, |
| }) : assert(localeName != null), |
| this._localeName = localeName, |
| assert(fullYearFormat != null), |
| this._fullYearFormat = fullYearFormat, |
| assert(mediumDateFormat != null), |
| this._mediumDateFormat = mediumDateFormat, |
| assert(longDateFormat != null), |
| this._longDateFormat = longDateFormat, |
| assert(yearMonthFormat != null), |
| this._yearMonthFormat = yearMonthFormat, |
| assert(decimalFormat != null), |
| this._decimalFormat = decimalFormat, |
| assert(twoDigitZeroPaddedFormat != null), |
| this._twoDigitZeroPaddedFormat = twoDigitZeroPaddedFormat; |
| |
| final String _localeName; |
| final intl.DateFormat _fullYearFormat; |
| final intl.DateFormat _mediumDateFormat; |
| final intl.DateFormat _longDateFormat; |
| final intl.DateFormat _yearMonthFormat; |
| final intl.NumberFormat _decimalFormat; |
| final intl.NumberFormat _twoDigitZeroPaddedFormat; |
| |
| @override |
| String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat = false }) { |
| switch (hourFormat(of: timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat))) { |
| case HourFormat.HH: |
| return _twoDigitZeroPaddedFormat.format(timeOfDay.hour); |
| case HourFormat.H: |
| return formatDecimal(timeOfDay.hour); |
| case HourFormat.h: |
| final int hour = timeOfDay.hourOfPeriod; |
| return formatDecimal(hour == 0 ? 12 : hour); |
| } |
| return null; |
| } |
| |
| @override |
| String formatMinute(TimeOfDay timeOfDay) { |
| return _twoDigitZeroPaddedFormat.format(timeOfDay.minute); |
| } |
| |
| @override |
| String formatYear(DateTime date) { |
| return _fullYearFormat.format(date); |
| } |
| |
| @override |
| String formatMediumDate(DateTime date) { |
| return _mediumDateFormat.format(date); |
| } |
| |
| @override |
| String formatFullDate(DateTime date) { |
| return _longDateFormat.format(date); |
| } |
| |
| @override |
| String formatMonthYear(DateTime date) { |
| return _yearMonthFormat.format(date); |
| } |
| |
| @override |
| List<String> get narrowWeekdays { |
| return _longDateFormat.dateSymbols.NARROWWEEKDAYS; |
| } |
| |
| @override |
| int get firstDayOfWeekIndex => (_longDateFormat.dateSymbols.FIRSTDAYOFWEEK + 1) % 7; |
| |
| @override |
| String formatDecimal(int number) { |
| return _decimalFormat.format(number); |
| } |
| |
| @override |
| String formatTimeOfDay(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat = false }) { |
| // Not using intl.DateFormat for two reasons: |
| // |
| // - DateFormat supports more formats than our material time picker does, |
| // and we want to be consistent across time picker format and the string |
| // formatting of the time of day. |
| // - DateFormat operates on DateTime, which is sensitive to time eras and |
| // time zones, while here we want to format hour and minute within one day |
| // no matter what date the day falls on. |
| final String hour = formatHour(timeOfDay, alwaysUse24HourFormat: alwaysUse24HourFormat); |
| final String minute = formatMinute(timeOfDay); |
| switch (timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat)) { |
| case TimeOfDayFormat.h_colon_mm_space_a: |
| return '$hour:$minute ${_formatDayPeriod(timeOfDay)}'; |
| case TimeOfDayFormat.H_colon_mm: |
| case TimeOfDayFormat.HH_colon_mm: |
| return '$hour:$minute'; |
| case TimeOfDayFormat.HH_dot_mm: |
| return '$hour.$minute'; |
| case TimeOfDayFormat.a_space_h_colon_mm: |
| return '${_formatDayPeriod(timeOfDay)} $hour:$minute'; |
| case TimeOfDayFormat.frenchCanadian: |
| return '$hour h $minute'; |
| } |
| return null; |
| } |
| |
| String _formatDayPeriod(TimeOfDay timeOfDay) { |
| switch (timeOfDay.period) { |
| case DayPeriod.am: |
| return anteMeridiemAbbreviation; |
| case DayPeriod.pm: |
| return postMeridiemAbbreviation; |
| } |
| return null; |
| } |
| |
| /// The raw version of [aboutListTileTitle], with `$applicationName` verbatim |
| /// in the string. |
| @protected |
| String get aboutListTileTitleRaw; |
| |
| @override |
| String aboutListTileTitle(String applicationName) { |
| final String text = aboutListTileTitleRaw; |
| return text.replaceFirst(r'$applicationName', applicationName); |
| } |
| |
| /// The raw version of [pageRowsInfoTitle], with `$firstRow`, `$lastRow`' and |
| /// `$rowCount` verbatim in the string, for the case where the value is |
| /// approximate. |
| @protected |
| String get pageRowsInfoTitleApproximateRaw; |
| |
| /// The raw version of [pageRowsInfoTitle], with `$firstRow`, `$lastRow`' and |
| /// `$rowCount` verbatim in the string, for the case where the value is |
| /// precise. |
| @protected |
| String get pageRowsInfoTitleRaw; |
| |
| @override |
| String pageRowsInfoTitle(int firstRow, int lastRow, int rowCount, bool rowCountIsApproximate) { |
| String text = rowCountIsApproximate ? pageRowsInfoTitleApproximateRaw : null; |
| text ??= pageRowsInfoTitleRaw; |
| assert(text != null, 'A $_localeName localization was not found for pageRowsInfoTitle or pageRowsInfoTitleApproximate'); |
| return text |
| .replaceFirst(r'$firstRow', formatDecimal(firstRow)) |
| .replaceFirst(r'$lastRow', formatDecimal(lastRow)) |
| .replaceFirst(r'$rowCount', formatDecimal(rowCount)); |
| } |
| |
| /// The raw version of [tabLabel], with `$tabIndex` and `$tabCount` verbatim |
| /// in the string. |
| @protected |
| String get tabLabelRaw; |
| |
| @override |
| String tabLabel({int tabIndex, int tabCount}) { |
| assert(tabIndex >= 1); |
| assert(tabCount >= 1); |
| final String template = tabLabelRaw; |
| return template |
| .replaceFirst(r'$tabIndex', formatDecimal(tabIndex)) |
| .replaceFirst(r'$tabCount', formatDecimal(tabCount)); |
| } |
| |
| /// The "zero" form of [selectedRowCountTitle]. |
| /// |
| /// This form is optional. |
| /// |
| /// See also: |
| /// |
| /// * [Intl.plural], to which this form is passed. |
| /// * [selectedRowCountTitleOne], the "one" form |
| /// * [selectedRowCountTitleTwo], the "two" form |
| /// * [selectedRowCountTitleFew], the "few" form |
| /// * [selectedRowCountTitleMany], the "many" form |
| /// * [selectedRowCountTitleOther], the "other" form |
| @protected |
| String get selectedRowCountTitleZero => null; |
| |
| /// The "one" form of [selectedRowCountTitle]. |
| /// |
| /// This form is optional. |
| /// |
| /// See also: |
| /// |
| /// * [Intl.plural], to which this form is passed. |
| /// * [selectedRowCountTitleZero], the "zero" form |
| /// * [selectedRowCountTitleTwo], the "two" form |
| /// * [selectedRowCountTitleFew], the "few" form |
| /// * [selectedRowCountTitleMany], the "many" form |
| /// * [selectedRowCountTitleOther], the "other" form |
| @protected |
| String get selectedRowCountTitleOne => null; |
| |
| /// The "two" form of [selectedRowCountTitle]. |
| /// |
| /// This form is optional. |
| /// |
| /// See also: |
| /// |
| /// * [Intl.plural], to which this form is passed. |
| /// * [selectedRowCountTitleZero], the "zero" form |
| /// * [selectedRowCountTitleOne], the "one" form |
| /// * [selectedRowCountTitleFew], the "few" form |
| /// * [selectedRowCountTitleMany], the "many" form |
| /// * [selectedRowCountTitleOther], the "other" form |
| @protected |
| String get selectedRowCountTitleTwo => null; |
| |
| /// The "few" form of [selectedRowCountTitle]. |
| /// |
| /// This form is optional. |
| /// |
| /// See also: |
| /// |
| /// * [Intl.plural], to which this form is passed. |
| /// * [selectedRowCountTitleZero], the "zero" form |
| /// * [selectedRowCountTitleOne], the "one" form |
| /// * [selectedRowCountTitleTwo], the "two" form |
| /// * [selectedRowCountTitleMany], the "many" form |
| /// * [selectedRowCountTitleOther], the "other" form |
| @protected |
| String get selectedRowCountTitleFew => null; |
| |
| /// The "many" form of [selectedRowCountTitle]. |
| /// |
| /// This form is optional. |
| /// |
| /// See also: |
| /// |
| /// * [Intl.plural], to which this form is passed. |
| /// * [selectedRowCountTitleZero], the "zero" form |
| /// * [selectedRowCountTitleOne], the "one" form |
| /// * [selectedRowCountTitleTwo], the "two" form |
| /// * [selectedRowCountTitleFew], the "few" form |
| /// * [selectedRowCountTitleOther], the "other" form |
| @protected |
| String get selectedRowCountTitleMany => null; |
| |
| /// The "other" form of [selectedRowCountTitle]. |
| /// |
| /// This form is required. |
| /// |
| /// See also: |
| /// |
| /// * [Intl.plural], to which this form is passed. |
| /// * [selectedRowCountTitleZero], the "zero" form |
| /// * [selectedRowCountTitleOne], the "one" form |
| /// * [selectedRowCountTitleTwo], the "two" form |
| /// * [selectedRowCountTitleFew], the "few" form |
| /// * [selectedRowCountTitleMany], the "many" form |
| @protected |
| String get selectedRowCountTitleOther; |
| |
| @override |
| String selectedRowCountTitle(int selectedRowCount) { |
| return intl.Intl.pluralLogic( |
| selectedRowCount, |
| zero: selectedRowCountTitleZero, |
| one: selectedRowCountTitleOne, |
| two: selectedRowCountTitleTwo, |
| few: selectedRowCountTitleFew, |
| many: selectedRowCountTitleMany, |
| other: selectedRowCountTitleOther, |
| locale: _localeName, |
| ).replaceFirst(r'$selectedRowCount', formatDecimal(selectedRowCount)); |
| } |
| |
| /// The format to use for [timeOfDayFormat]. |
| @protected |
| TimeOfDayFormat get timeOfDayFormatRaw; |
| |
| /// The [TimeOfDayFormat] corresponding to one of the following supported |
| /// patterns: |
| /// |
| /// * `HH:mm` |
| /// * `HH.mm` |
| /// * `HH 'h' mm` |
| /// * `HH:mm น.` |
| /// * `H:mm` |
| /// * `h:mm a` |
| /// * `a h:mm` |
| /// * `ah:mm` |
| /// |
| /// See also: |
| /// |
| /// * <http://demo.icu-project.org/icu-bin/locexp?d_=en&_=en_US>, which shows |
| /// the short time pattern used in the `en_US` locale. |
| @override |
| TimeOfDayFormat timeOfDayFormat({ bool alwaysUse24HourFormat = false }) { |
| assert(alwaysUse24HourFormat != null); |
| if (alwaysUse24HourFormat) |
| return _get24HourVersionOf(timeOfDayFormatRaw); |
| return timeOfDayFormatRaw; |
| } |
| |
| /// The "zero" form of [remainingTextFieldCharacterCount]. |
| /// |
| /// This form is required. |
| /// |
| /// See also: |
| /// |
| /// * [Intl.plural], to which this form is passed. |
| /// * [remainingTextFieldCharacterCountZero], the "zero" form |
| /// * [remainingTextFieldCharacterCountOne], the "one" form |
| /// * [remainingTextFieldCharacterCountOther], the "other" form |
| @protected |
| String get remainingTextFieldCharacterCountZero; |
| |
| /// The "one" form of [remainingTextFieldCharacterCount]. |
| /// |
| /// This form is optional. |
| /// |
| /// See also: |
| /// |
| /// * [Intl.plural], to which this form is passed. |
| /// * [remainingTextFieldCharacterCountZero], the "zero" form |
| /// * [remainingTextFieldCharacterCountOne], the "one" form |
| /// * [remainingTextFieldCharacterCountOther], the "other" form |
| @protected |
| String get remainingTextFieldCharacterCountOne => null; |
| |
| /// The "other" form of [remainingTextFieldCharacterCount]. |
| /// |
| /// This form is required. |
| /// |
| /// See also: |
| /// |
| /// * [Intl.plural], to which this form is passed. |
| /// * [remainingTextFieldCharacterCountZero], the "zero" form |
| /// * [remainingTextFieldCharacterCountOne], the "one" form |
| /// * [remainingTextFieldCharacterCountOther], the "other" form |
| @protected |
| String get remainingTextFieldCharacterCountOther; |
| |
| @override |
| String remainingTextFieldCharacterCount(int remainingCount) { |
| return intl.Intl.pluralLogic( |
| remainingCount, |
| zero: remainingTextFieldCharacterCountZero, |
| one: remainingTextFieldCharacterCountOne, |
| other: remainingTextFieldCharacterCountOther, |
| locale: _localeName, |
| ).replaceFirst(r'$remainingCount', formatDecimal(remainingCount)); |
| } |
| |
| /// The script category used by [localTextGeometry]. Must be one of the strings |
| /// declared in [MaterialTextGeometry]. |
| // |
| // TODO(ianh): make this return a TextTheme from MaterialTextGeometry. |
| // TODO(ianh): drop the constructor on MaterialTextGeometry. |
| // TODO(ianh): drop the strings on MaterialTextGeometry. |
| @protected |
| String get scriptCategory; |
| |
| /// Looks up text geometry defined in [MaterialTextGeometry]. |
| @override |
| TextTheme get localTextGeometry => MaterialTextGeometry.forScriptCategory(scriptCategory); |
| |
| /// A [LocalizationsDelegate] that uses [GlobalMaterialLocalizations.load] |
| /// to create an instance of this class. |
| /// |
| /// Most internationalized apps will use [GlobalMaterialLocalizations.delegates] |
| /// as the value of [MaterialApp.localizationsDelegates] to include |
| /// the localizations for both the material and widget libraries. |
| static const LocalizationsDelegate<MaterialLocalizations> delegate = _MaterialLocalizationsDelegate(); |
| |
| /// A value for [MaterialApp.localizationsDelegates] that's typically used by |
| /// internationalized apps. |
| /// |
| /// ## Sample code |
| /// |
| /// To include the localizations provided by this class and by |
| /// [GlobalWidgetsLocalizations] in a [MaterialApp], |
| /// use [GlobalMaterialLocalizations.delegates] as the value of |
| /// [MaterialApp.localizationsDelegates], and specify the locales your |
| /// app supports with [MaterialApp.supportedLocales]: |
| /// |
| /// ```dart |
| /// new MaterialApp( |
| /// localizationsDelegates: GlobalMaterialLocalizations.delegates, |
| /// supportedLocales: [ |
| /// const Locale('en', 'US'), // English |
| /// const Locale('he', 'IL'), // Hebrew |
| /// ], |
| /// // ... |
| /// ) |
| /// ``` |
| static const List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[ |
| GlobalMaterialLocalizations.delegate, |
| GlobalWidgetsLocalizations.delegate, |
| ]; |
| } |
| |
| /// Finds the [TimeOfDayFormat] to use instead of the `original` when the |
| /// `original` uses 12-hour format and [MediaQueryData.alwaysUse24HourFormat] |
| /// is true. |
| TimeOfDayFormat _get24HourVersionOf(TimeOfDayFormat original) { |
| switch (original) { |
| case TimeOfDayFormat.HH_colon_mm: |
| case TimeOfDayFormat.HH_dot_mm: |
| case TimeOfDayFormat.frenchCanadian: |
| case TimeOfDayFormat.H_colon_mm: |
| return original; |
| case TimeOfDayFormat.h_colon_mm_space_a: |
| case TimeOfDayFormat.a_space_h_colon_mm: |
| return TimeOfDayFormat.HH_colon_mm; |
| } |
| return TimeOfDayFormat.HH_colon_mm; |
| } |
| |
| class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> { |
| const _MaterialLocalizationsDelegate(); |
| |
| @override |
| bool isSupported(Locale locale) => kSupportedLanguages.contains(locale.languageCode); |
| |
| /// Tracks if date i18n data has been loaded. |
| static bool _dateIntlDataInitialized = false; |
| |
| /// Loads i18n data for dates if it hasn't be loaded yet. |
| /// |
| /// Only the first invocation of this function has the effect of loading the |
| /// data. Subsequent invocations have no effect. |
| static void _loadDateIntlDataIfNotLoaded() { |
| if (!_dateIntlDataInitialized) { |
| date_localizations.dateSymbols.forEach((String locale, dynamic data) { |
| assert(date_localizations.datePatterns.containsKey(locale)); |
| final intl.DateSymbols symbols = new intl.DateSymbols.deserializeFromMap(data); |
| date_symbol_data_custom.initializeDateFormattingCustom( |
| locale: locale, |
| symbols: symbols, |
| patterns: date_localizations.datePatterns[locale], |
| ); |
| }); |
| _dateIntlDataInitialized = true; |
| } |
| } |
| |
| static final Map<Locale, Future<MaterialLocalizations>> _loadedTranslations = <Locale, Future<MaterialLocalizations>>{}; |
| |
| @override |
| Future<MaterialLocalizations> load(Locale locale) { |
| assert(isSupported(locale)); |
| return _loadedTranslations.putIfAbsent(locale, () { |
| _loadDateIntlDataIfNotLoaded(); |
| |
| final String localeName = intl.Intl.canonicalizedLocale(locale.toString()); |
| |
| intl.DateFormat fullYearFormat; |
| intl.DateFormat mediumDateFormat; |
| intl.DateFormat longDateFormat; |
| intl.DateFormat yearMonthFormat; |
| if (intl.DateFormat.localeExists(localeName)) { |
| fullYearFormat = new intl.DateFormat.y(localeName); |
| mediumDateFormat = new intl.DateFormat.MMMEd(localeName); |
| longDateFormat = new intl.DateFormat.yMMMMEEEEd(localeName); |
| yearMonthFormat = new intl.DateFormat.yMMMM(localeName); |
| } else if (intl.DateFormat.localeExists(locale.languageCode)) { |
| fullYearFormat = new intl.DateFormat.y(locale.languageCode); |
| mediumDateFormat = new intl.DateFormat.MMMEd(locale.languageCode); |
| longDateFormat = new intl.DateFormat.yMMMMEEEEd(locale.languageCode); |
| yearMonthFormat = new intl.DateFormat.yMMMM(locale.languageCode); |
| } else { |
| fullYearFormat = new intl.DateFormat.y(); |
| mediumDateFormat = new intl.DateFormat.MMMEd(); |
| longDateFormat = new intl.DateFormat.yMMMMEEEEd(); |
| yearMonthFormat = new intl.DateFormat.yMMMM(); |
| } |
| |
| intl.NumberFormat decimalFormat; |
| intl.NumberFormat twoDigitZeroPaddedFormat; |
| if (intl.NumberFormat.localeExists(localeName)) { |
| decimalFormat = new intl.NumberFormat.decimalPattern(localeName); |
| twoDigitZeroPaddedFormat = new intl.NumberFormat('00', localeName); |
| } else if (intl.NumberFormat.localeExists(locale.languageCode)) { |
| decimalFormat = new intl.NumberFormat.decimalPattern(locale.languageCode); |
| twoDigitZeroPaddedFormat = new intl.NumberFormat('00', locale.languageCode); |
| } else { |
| decimalFormat = new intl.NumberFormat.decimalPattern(); |
| twoDigitZeroPaddedFormat = new intl.NumberFormat('00'); |
| } |
| |
| assert(locale.toString() == localeName, 'comparing "$locale" to "$localeName"'); |
| |
| return new SynchronousFuture<MaterialLocalizations>(getTranslation( |
| locale, |
| fullYearFormat, |
| mediumDateFormat, |
| longDateFormat, |
| yearMonthFormat, |
| decimalFormat, |
| twoDigitZeroPaddedFormat, |
| )); |
| }); |
| } |
| |
| @override |
| bool shouldReload(_MaterialLocalizationsDelegate old) => false; |
| } |