Cupertino localization step 6: add a GlobalCupertinoLocalizations base class with date time formatting (#29767)
diff --git a/packages/flutter_localizations/lib/src/cupertino_localizations.dart b/packages/flutter_localizations/lib/src/cupertino_localizations.dart
new file mode 100644
index 0000000..557a934
--- /dev/null
+++ b/packages/flutter_localizations/lib/src/cupertino_localizations.dart
@@ -0,0 +1,439 @@
+// Copyright 2019 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/cupertino.dart';
+import 'package:flutter/foundation.dart';
+import 'package:intl/intl.dart' as intl;
+import 'package:intl/date_symbols.dart' as intl;
+
+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]
+///
+/// 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;
+ }
+ }
+
+ @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] that uses [GlobalCupertinoLocalizations.load]
+ /// to create an instance of this class.
+ ///
+ /// 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) => false; // TODO(xster): implement.
+
+ static final Map<Locale, Future<CupertinoLocalizations>> _loadedTranslations = <Locale, Future<CupertinoLocalizations>>{};
+
+ @override
+ Future<CupertinoLocalizations> load(Locale locale) {
+ assert(isSupported(locale)); // TODO(xster): implement.
+ 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(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(
+ localeName,
+ fullYearFormat,
+ dayFormat,
+ mediumDateFormat,
+ singleDigitHourFormat,
+ singleDigitMinuteFormat,
+ doubleDigitMinuteFormat,
+ singleDigitSecondFormat,
+ decimalFormat,
+ ));
+ });
+ }
+
+ @override
+ bool shouldReload(_GlobalCupertinoLocalizationsDelegate old) => false;
+}
+
+CupertinoLocalizations _getCupertinoTranslation(
+ String localeName,
+ intl.DateFormat fullYearFormat,
+ intl.DateFormat dayFormat,
+ intl.DateFormat mediumDateFormat,
+ intl.DateFormat singleDigitHourFormat,
+ intl.DateFormat singleDigitMinuteFormat,
+ intl.DateFormat doubleDigitMinuteFormat,
+ intl.DateFormat singleDigitSecondFormat,
+ intl.NumberFormat decimalFormat,
+) {
+ return null; // TODO(xster): implement in generated subclass.
+}