// 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)); } @override List<String> get timerPickerHourLabels => <String>[ timerPickerHourLabelZero, timerPickerHourLabelOne, timerPickerHourLabelTwo, timerPickerHourLabelFew, timerPickerHourLabelMany, timerPickerHourLabelOther, ]; /// 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)); } @override List<String> get timerPickerMinuteLabels => <String>[ timerPickerMinuteLabelZero, timerPickerMinuteLabelOne, timerPickerMinuteLabelTwo, timerPickerMinuteLabelFew, timerPickerMinuteLabelMany, timerPickerMinuteLabelOther, ]; /// 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)); } @override List<String> get timerPickerSecondLabels => <String>[ timerPickerSecondLabelZero, timerPickerSecondLabelOne, timerPickerSecondLabelTwo, timerPickerSecondLabelFew, timerPickerSecondLabelMany, timerPickerSecondLabelOther, ]; /// 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)'; }