blob: 0f318d28f1dcc1544941205bd35dd0a5fe9d837d [file] [log] [blame]
// 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 'dart:async';
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)';
}