| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'package:flutter/cupertino.dart'; | |
| import 'package:flutter/foundation.dart'; | |
| import 'package:intl/intl.dart' as intl; | |
| import 'l10n/generated_cupertino_localizations.dart'; | |
| import 'utils/date_localizations.dart' as util; | |
| import 'widgets_localizations.dart'; | |
| /// Implementation of localized strings for Cupertino widgets using the `intl` | |
| /// package for date and time formatting. | |
| /// | |
| /// Further localization of strings beyond date time formatting are provided | |
| /// by language specific subclasses of [GlobalCupertinoLocalizations]. | |
| /// | |
| /// ## Supported languages | |
| /// | |
| /// This class supports locales with the following [Locale.languageCode]s: | |
| /// | |
| /// {@macro flutter.localizations.cupertino.languages} | |
| /// | |
| /// This list is available programmatically via [kCupertinoSupportedLanguages]. | |
| /// | |
| /// ## Sample code | |
| /// | |
| /// To include the localizations provided by this class in a [CupertinoApp], | |
| /// add [GlobalCupertinoLocalizations.delegates] to | |
| /// [CupertinoApp.localizationsDelegates], and specify the locales your | |
| /// app supports with [CupertinoApp.supportedLocales]: | |
| /// | |
| /// ```dart | |
| /// new CupertinoApp( | |
| /// localizationsDelegates: GlobalCupertinoLocalizations.delegates, | |
| /// supportedLocales: [ | |
| /// const Locale('en', 'US'), // American English | |
| /// const Locale('he', 'IL'), // Israeli Hebrew | |
| /// // ... | |
| /// ], | |
| /// // ... | |
| /// ) | |
| /// ``` | |
| /// | |
| /// See also: | |
| /// | |
| /// * [DefaultCupertinoLocalizations], which provides US English localizations | |
| /// for Cupertino widgets. | |
| abstract class GlobalCupertinoLocalizations implements CupertinoLocalizations { | |
| /// Initializes an object that defines the Cupertino widgets' localized | |
| /// strings for the given `localeName`. | |
| /// | |
| /// The remaining '*Format' arguments uses the intl package to provide | |
| /// [DateFormat] configurations for the `localeName`. | |
| const GlobalCupertinoLocalizations({ | |
| @required String localeName, | |
| @required intl.DateFormat fullYearFormat, | |
| @required intl.DateFormat dayFormat, | |
| @required intl.DateFormat mediumDateFormat, | |
| @required intl.DateFormat singleDigitHourFormat, | |
| @required intl.DateFormat singleDigitMinuteFormat, | |
| @required intl.DateFormat doubleDigitMinuteFormat, | |
| @required intl.DateFormat singleDigitSecondFormat, | |
| @required intl.NumberFormat decimalFormat, | |
| }) : assert(localeName != null), | |
| _localeName = localeName, | |
| assert(fullYearFormat != null), | |
| _fullYearFormat = fullYearFormat, | |
| assert(dayFormat != null), | |
| _dayFormat = dayFormat, | |
| assert(mediumDateFormat != null), | |
| _mediumDateFormat = mediumDateFormat, | |
| assert(singleDigitHourFormat != null), | |
| _singleDigitHourFormat = singleDigitHourFormat, | |
| assert(singleDigitMinuteFormat != null), | |
| _singleDigitMinuteFormat = singleDigitMinuteFormat, | |
| assert(doubleDigitMinuteFormat != null), | |
| _doubleDigitMinuteFormat = doubleDigitMinuteFormat, | |
| assert(singleDigitSecondFormat != null), | |
| _singleDigitSecondFormat = singleDigitSecondFormat, | |
| assert(decimalFormat != null), | |
| _decimalFormat =decimalFormat; | |
| final String _localeName; | |
| final intl.DateFormat _fullYearFormat; | |
| final intl.DateFormat _dayFormat; | |
| final intl.DateFormat _mediumDateFormat; | |
| final intl.DateFormat _singleDigitHourFormat; | |
| final intl.DateFormat _singleDigitMinuteFormat; | |
| final intl.DateFormat _doubleDigitMinuteFormat; | |
| final intl.DateFormat _singleDigitSecondFormat; | |
| final intl.NumberFormat _decimalFormat; | |
| @override | |
| String datePickerYear(int yearIndex) { | |
| return _fullYearFormat.format(DateTime.utc(yearIndex)); | |
| } | |
| @override | |
| String datePickerMonth(int monthIndex) { | |
| // It doesn't actually have anything to do with _fullYearFormat. It's just | |
| // taking advantage of the fact that _fullYearFormat loaded the needed | |
| // locale's symbols. | |
| return _fullYearFormat.dateSymbols.MONTHS[monthIndex - 1]; | |
| } | |
| @override | |
| String datePickerDayOfMonth(int dayIndex) { | |
| // Year and month doesn't matter since we just want to day formatted. | |
| return _dayFormat.format(DateTime.utc(0, 0, dayIndex)); | |
| } | |
| @override | |
| String datePickerMediumDate(DateTime date) { | |
| return _mediumDateFormat.format(date); | |
| } | |
| @override | |
| String datePickerHour(int hour) { | |
| return _singleDigitHourFormat.format(DateTime.utc(0, 0, 0, hour)); | |
| } | |
| @override | |
| String datePickerMinute(int minute) { | |
| return _doubleDigitMinuteFormat.format(DateTime.utc(0, 0, 0, 0, minute)); | |
| } | |
| /// Subclasses should provide the optional zero pluralization of [datePickerHourSemanticsLabel] based on the ARB file. | |
| @protected String get datePickerHourSemanticsLabelZero => null; | |
| /// Subclasses should provide the optional one pluralization of [datePickerHourSemanticsLabel] based on the ARB file. | |
| @protected String get datePickerHourSemanticsLabelOne => null; | |
| /// Subclasses should provide the optional two pluralization of [datePickerHourSemanticsLabel] based on the ARB file. | |
| @protected String get datePickerHourSemanticsLabelTwo => null; | |
| /// Subclasses should provide the optional few pluralization of [datePickerHourSemanticsLabel] based on the ARB file. | |
| @protected String get datePickerHourSemanticsLabelFew => null; | |
| /// Subclasses should provide the optional many pluralization of [datePickerHourSemanticsLabel] based on the ARB file. | |
| @protected String get datePickerHourSemanticsLabelMany => null; | |
| /// Subclasses should provide the required other pluralization of [datePickerHourSemanticsLabel] based on the ARB file. | |
| @protected String get datePickerHourSemanticsLabelOther; | |
| @override | |
| String datePickerHourSemanticsLabel(int hour) { | |
| return intl.Intl.pluralLogic( | |
| hour, | |
| zero: datePickerHourSemanticsLabelZero, | |
| one: datePickerHourSemanticsLabelOne, | |
| two: datePickerHourSemanticsLabelTwo, | |
| few: datePickerHourSemanticsLabelFew, | |
| many: datePickerHourSemanticsLabelMany, | |
| other: datePickerHourSemanticsLabelOther, | |
| locale: _localeName, | |
| ).replaceFirst(r'$hour', _decimalFormat.format(hour)); | |
| } | |
| /// Subclasses should provide the optional zero pluralization of [datePickerMinuteSemanticsLabel] based on the ARB file. | |
| @protected String get datePickerMinuteSemanticsLabelZero => null; | |
| /// Subclasses should provide the optional one pluralization of [datePickerMinuteSemanticsLabel] based on the ARB file. | |
| @protected String get datePickerMinuteSemanticsLabelOne => null; | |
| /// Subclasses should provide the optional two pluralization of [datePickerMinuteSemanticsLabel] based on the ARB file. | |
| @protected String get datePickerMinuteSemanticsLabelTwo => null; | |
| /// Subclasses should provide the optional few pluralization of [datePickerMinuteSemanticsLabel] based on the ARB file. | |
| @protected String get datePickerMinuteSemanticsLabelFew => null; | |
| /// Subclasses should provide the optional many pluralization of [datePickerMinuteSemanticsLabel] based on the ARB file. | |
| @protected String get datePickerMinuteSemanticsLabelMany => null; | |
| /// Subclasses should provide the required other pluralization of [datePickerMinuteSemanticsLabel] based on the ARB file. | |
| @protected String get datePickerMinuteSemanticsLabelOther; | |
| @override | |
| String datePickerMinuteSemanticsLabel(int minute) { | |
| return intl.Intl.pluralLogic( | |
| minute, | |
| zero: datePickerMinuteSemanticsLabelZero, | |
| one: datePickerMinuteSemanticsLabelOne, | |
| two: datePickerMinuteSemanticsLabelTwo, | |
| few: datePickerMinuteSemanticsLabelFew, | |
| many: datePickerMinuteSemanticsLabelMany, | |
| other: datePickerMinuteSemanticsLabelOther, | |
| locale: _localeName, | |
| ).replaceFirst(r'$minute', _decimalFormat.format(minute)); | |
| } | |
| /// A string describing the [DatePickerDateOrder] enum value. | |
| /// | |
| /// Subclasses should provide this string value based on the ARB file for | |
| /// the locale. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [datePickerDateOrder], which provides the [DatePickerDateOrder] | |
| /// enum value for [CupertinoLocalizations] based on this string value | |
| @protected | |
| String get datePickerDateOrderString; | |
| @override | |
| DatePickerDateOrder get datePickerDateOrder { | |
| switch (datePickerDateOrderString) { | |
| case 'dmy': | |
| return DatePickerDateOrder.dmy; | |
| case 'mdy': | |
| return DatePickerDateOrder.mdy; | |
| case 'ymd': | |
| return DatePickerDateOrder.ymd; | |
| case 'ydm': | |
| return DatePickerDateOrder.ydm; | |
| default: | |
| assert( | |
| false, | |
| 'Failed to load DatePickerDateOrder $datePickerDateOrderString for ' | |
| "locale $_localeName.\nNon conforming string for $_localeName's " | |
| '.arb file', | |
| ); | |
| return null; | |
| } | |
| } | |
| /// A string describing the [DatePickerDateTimeOrder] enum value. | |
| /// | |
| /// Subclasses should provide this string value based on the ARB file for | |
| /// the locale. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [datePickerDateTimeOrder], which provides the [DatePickerDateTimeOrder] | |
| /// enum value for [CupertinoLocalizations] based on this string value. | |
| @protected | |
| String get datePickerDateTimeOrderString; | |
| @override | |
| DatePickerDateTimeOrder get datePickerDateTimeOrder { | |
| switch (datePickerDateTimeOrderString) { | |
| case 'date_time_dayPeriod': | |
| return DatePickerDateTimeOrder.date_time_dayPeriod; | |
| case 'date_dayPeriod_time': | |
| return DatePickerDateTimeOrder.date_dayPeriod_time; | |
| case 'time_dayPeriod_date': | |
| return DatePickerDateTimeOrder.time_dayPeriod_date; | |
| case 'dayPeriod_time_date': | |
| return DatePickerDateTimeOrder.dayPeriod_time_date; | |
| default: | |
| assert( | |
| false, | |
| 'Failed to load DatePickerDateTimeOrder $datePickerDateTimeOrderString ' | |
| "for locale $_localeName.\nNon conforming string for $_localeName's " | |
| '.arb file', | |
| ); | |
| return null; | |
| } | |
| } | |
| /// The raw version of [tabSemanticsLabel], with `$tabIndex` and `$tabCount` verbatim | |
| /// in the string. | |
| @protected | |
| String get tabSemanticsLabelRaw; | |
| @override | |
| String tabSemanticsLabel({ int tabIndex, int tabCount }) { | |
| assert(tabIndex >= 1); | |
| assert(tabCount >= 1); | |
| final String template = tabSemanticsLabelRaw; | |
| return template | |
| .replaceFirst(r'$tabIndex', _decimalFormat.format(tabIndex)) | |
| .replaceFirst(r'$tabCount', _decimalFormat.format(tabCount)); | |
| } | |
| @override | |
| String timerPickerHour(int hour) { | |
| return _singleDigitHourFormat.format(DateTime.utc(0, 0, 0, hour)); | |
| } | |
| @override | |
| String timerPickerMinute(int minute) { | |
| return _singleDigitMinuteFormat.format(DateTime.utc(0, 0, 0, 0, minute)); | |
| } | |
| @override | |
| String timerPickerSecond(int second) { | |
| return _singleDigitSecondFormat.format(DateTime.utc(0, 0, 0, 0, 0, second)); | |
| } | |
| /// Subclasses should provide the optional zero pluralization of [timerPickerHourLabel] based on the ARB file. | |
| @protected String get timerPickerHourLabelZero => null; | |
| /// Subclasses should provide the optional one pluralization of [timerPickerHourLabel] based on the ARB file. | |
| @protected String get timerPickerHourLabelOne => null; | |
| /// Subclasses should provide the optional two pluralization of [timerPickerHourLabel] based on the ARB file. | |
| @protected String get timerPickerHourLabelTwo => null; | |
| /// Subclasses should provide the optional few pluralization of [timerPickerHourLabel] based on the ARB file. | |
| @protected String get timerPickerHourLabelFew => null; | |
| /// Subclasses should provide the optional many pluralization of [timerPickerHourLabel] based on the ARB file. | |
| @protected String get timerPickerHourLabelMany => null; | |
| /// Subclasses should provide the required other pluralization of [timerPickerHourLabel] based on the ARB file. | |
| @protected String get timerPickerHourLabelOther; | |
| @override | |
| String timerPickerHourLabel(int hour) { | |
| return intl.Intl.pluralLogic( | |
| hour, | |
| zero: timerPickerHourLabelZero, | |
| one: timerPickerHourLabelOne, | |
| two: timerPickerHourLabelTwo, | |
| few: timerPickerHourLabelFew, | |
| many: timerPickerHourLabelMany, | |
| other: timerPickerHourLabelOther, | |
| locale: _localeName, | |
| ).replaceFirst(r'$hour', _decimalFormat.format(hour)); | |
| } | |
| /// Subclasses should provide the optional zero pluralization of [timerPickerMinuteLabel] based on the ARB file. | |
| @protected String get timerPickerMinuteLabelZero => null; | |
| /// Subclasses should provide the optional one pluralization of [timerPickerMinuteLabel] based on the ARB file. | |
| @protected String get timerPickerMinuteLabelOne => null; | |
| /// Subclasses should provide the optional two pluralization of [timerPickerMinuteLabel] based on the ARB file. | |
| @protected String get timerPickerMinuteLabelTwo => null; | |
| /// Subclasses should provide the optional few pluralization of [timerPickerMinuteLabel] based on the ARB file. | |
| @protected String get timerPickerMinuteLabelFew => null; | |
| /// Subclasses should provide the optional many pluralization of [timerPickerMinuteLabel] based on the ARB file. | |
| @protected String get timerPickerMinuteLabelMany => null; | |
| /// Subclasses should provide the required other pluralization of [timerPickerMinuteLabel] based on the ARB file. | |
| @protected String get timerPickerMinuteLabelOther; | |
| @override | |
| String timerPickerMinuteLabel(int minute) { | |
| return intl.Intl.pluralLogic( | |
| minute, | |
| zero: timerPickerMinuteLabelZero, | |
| one: timerPickerMinuteLabelOne, | |
| two: timerPickerMinuteLabelTwo, | |
| few: timerPickerMinuteLabelFew, | |
| many: timerPickerMinuteLabelMany, | |
| other: timerPickerMinuteLabelOther, | |
| locale: _localeName, | |
| ).replaceFirst(r'$minute', _decimalFormat.format(minute)); | |
| } | |
| /// Subclasses should provide the optional zero pluralization of [timerPickerSecondLabel] based on the ARB file. | |
| @protected String get timerPickerSecondLabelZero => null; | |
| /// Subclasses should provide the optional one pluralization of [timerPickerSecondLabel] based on the ARB file. | |
| @protected String get timerPickerSecondLabelOne => null; | |
| /// Subclasses should provide the optional two pluralization of [timerPickerSecondLabel] based on the ARB file. | |
| @protected String get timerPickerSecondLabelTwo => null; | |
| /// Subclasses should provide the optional few pluralization of [timerPickerSecondLabel] based on the ARB file. | |
| @protected String get timerPickerSecondLabelFew => null; | |
| /// Subclasses should provide the optional many pluralization of [timerPickerSecondLabel] based on the ARB file. | |
| @protected String get timerPickerSecondLabelMany => null; | |
| /// Subclasses should provide the required other pluralization of [timerPickerSecondLabel] based on the ARB file. | |
| @protected String get timerPickerSecondLabelOther; | |
| @override | |
| String timerPickerSecondLabel(int second) { | |
| return intl.Intl.pluralLogic( | |
| second, | |
| zero: timerPickerSecondLabelZero, | |
| one: timerPickerSecondLabelOne, | |
| two: timerPickerSecondLabelTwo, | |
| few: timerPickerSecondLabelFew, | |
| many: timerPickerSecondLabelMany, | |
| other: timerPickerSecondLabelOther, | |
| locale: _localeName, | |
| ).replaceFirst(r'$second', _decimalFormat.format(second)); | |
| } | |
| /// A [LocalizationsDelegate] for [CupertinoLocalizations]. | |
| /// | |
| /// Most internationalized apps will use [GlobalCupertinoLocalizations.delegates] | |
| /// as the value of [CupertinoApp.localizationsDelegates] to include | |
| /// the localizations for both the cupertino and widget libraries. | |
| static const LocalizationsDelegate<CupertinoLocalizations> delegate = _GlobalCupertinoLocalizationsDelegate(); | |
| /// A value for [CupertinoApp.localizationsDelegates] that's typically used by | |
| /// internationalized apps. | |
| /// | |
| /// ## Sample code | |
| /// | |
| /// To include the localizations provided by this class and by | |
| /// [GlobalWidgetsLocalizations] in a [CupertinoApp], | |
| /// use [GlobalCupertinoLocalizations.delegates] as the value of | |
| /// [CupertinoApp.localizationsDelegates], and specify the locales your | |
| /// app supports with [CupertinoApp.supportedLocales]: | |
| /// | |
| /// ```dart | |
| /// new CupertinoApp( | |
| /// localizationsDelegates: GlobalCupertinoLocalizations.delegates, | |
| /// supportedLocales: [ | |
| /// const Locale('en', 'US'), // English | |
| /// const Locale('he', 'IL'), // Hebrew | |
| /// ], | |
| /// // ... | |
| /// ) | |
| /// ``` | |
| static const List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[ | |
| GlobalCupertinoLocalizations.delegate, | |
| GlobalWidgetsLocalizations.delegate, | |
| ]; | |
| } | |
| class _GlobalCupertinoLocalizationsDelegate extends LocalizationsDelegate<CupertinoLocalizations> { | |
| const _GlobalCupertinoLocalizationsDelegate(); | |
| @override | |
| bool isSupported(Locale locale) => kCupertinoSupportedLanguages.contains(locale.languageCode); | |
| static final Map<Locale, Future<CupertinoLocalizations>> _loadedTranslations = <Locale, Future<CupertinoLocalizations>>{}; | |
| @override | |
| Future<CupertinoLocalizations> load(Locale locale) { | |
| assert(isSupported(locale)); | |
| return _loadedTranslations.putIfAbsent(locale, () { | |
| util.loadDateIntlDataIfNotLoaded(); | |
| final String localeName = intl.Intl.canonicalizedLocale(locale.toString()); | |
| assert( | |
| locale.toString() == localeName, | |
| 'Flutter does not support the non-standard locale form $locale (which ' | |
| 'might be $localeName', | |
| ); | |
| intl.DateFormat fullYearFormat; | |
| intl.DateFormat dayFormat; | |
| intl.DateFormat mediumDateFormat; | |
| // We don't want any additional decoration here. The am/pm is handled in | |
| // the date picker. We just want an hour number localized. | |
| intl.DateFormat singleDigitHourFormat; | |
| intl.DateFormat singleDigitMinuteFormat; | |
| intl.DateFormat doubleDigitMinuteFormat; | |
| intl.DateFormat singleDigitSecondFormat; | |
| intl.NumberFormat decimalFormat; | |
| void loadFormats(String locale) { | |
| fullYearFormat = intl.DateFormat.y(locale); | |
| dayFormat = intl.DateFormat.d(locale); | |
| mediumDateFormat = intl.DateFormat.MMMEd(locale); | |
| // TODO(xster): fix when https://github.com/dart-lang/intl/issues/207 is resolved. | |
| singleDigitHourFormat = intl.DateFormat('HH', locale); | |
| singleDigitMinuteFormat = intl.DateFormat.m(locale); | |
| doubleDigitMinuteFormat = intl.DateFormat('mm', locale); | |
| singleDigitSecondFormat = intl.DateFormat.s(locale); | |
| decimalFormat = intl.NumberFormat.decimalPattern(locale); | |
| } | |
| if (intl.DateFormat.localeExists(localeName)) { | |
| loadFormats(localeName); | |
| } else if (intl.DateFormat.localeExists(locale.languageCode)) { | |
| loadFormats(locale.languageCode); | |
| } else { | |
| loadFormats(null); | |
| } | |
| return SynchronousFuture<CupertinoLocalizations>(getCupertinoTranslation( | |
| locale, | |
| fullYearFormat, | |
| dayFormat, | |
| mediumDateFormat, | |
| singleDigitHourFormat, | |
| singleDigitMinuteFormat, | |
| doubleDigitMinuteFormat, | |
| singleDigitSecondFormat, | |
| decimalFormat, | |
| )); | |
| }); | |
| } | |
| @override | |
| bool shouldReload(_GlobalCupertinoLocalizationsDelegate old) => false; | |
| @override | |
| String toString() => 'GlobalCupertinoLocalizations.delegate(${kCupertinoSupportedLanguages.length} locales)'; | |
| } |