• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6
7import 'package:flutter/cupertino.dart';
8import 'package:flutter/foundation.dart';
9import 'package:intl/intl.dart' as intl;
10import 'package:intl/date_symbols.dart' as intl;
11
12import 'l10n/generated_cupertino_localizations.dart';
13import 'utils/date_localizations.dart' as util;
14import 'widgets_localizations.dart';
15
16/// Implementation of localized strings for Cupertino widgets using the `intl`
17/// package for date and time formatting.
18///
19/// Further localization of strings beyond date time formatting are provided
20/// by language specific subclasses of [GlobalCupertinoLocalizations].
21///
22/// ## Supported languages
23///
24/// This class supports locales with the following [Locale.languageCode]s:
25///
26/// {@macro flutter.localizations.cupertino.languages}
27///
28/// This list is available programatically via [kCupertinoSupportedLanguages].
29///
30/// ## Sample code
31///
32/// To include the localizations provided by this class in a [CupertinoApp],
33/// add [GlobalCupertinoLocalizations.delegates] to
34/// [CupertinoApp.localizationsDelegates], and specify the locales your
35/// app supports with [CupertinoApp.supportedLocales]:
36///
37/// ```dart
38/// new CupertinoApp(
39///   localizationsDelegates: GlobalCupertinoLocalizations.delegates,
40///   supportedLocales: [
41///     const Locale('en', 'US'), // American English
42///     const Locale('he', 'IL'), // Israeli Hebrew
43///     // ...
44///   ],
45///   // ...
46/// )
47/// ```
48///
49/// See also:
50///
51///  * [DefaultCupertinoLocalizations], which provides US English localizations
52///    for Cupertino widgets.
53abstract class GlobalCupertinoLocalizations implements CupertinoLocalizations {
54  /// Initializes an object that defines the Cupertino widgets' localized
55  /// strings for the given `localeName`.
56  ///
57  /// The remaining '*Format' arguments uses the intl package to provide
58  /// [DateFormat] configurations for the `localeName`.
59  const GlobalCupertinoLocalizations({
60    @required String localeName,
61    @required intl.DateFormat fullYearFormat,
62    @required intl.DateFormat dayFormat,
63    @required intl.DateFormat mediumDateFormat,
64    @required intl.DateFormat singleDigitHourFormat,
65    @required intl.DateFormat singleDigitMinuteFormat,
66    @required intl.DateFormat doubleDigitMinuteFormat,
67    @required intl.DateFormat singleDigitSecondFormat,
68    @required intl.NumberFormat decimalFormat,
69  }) : assert(localeName != null),
70       _localeName = localeName,
71       assert(fullYearFormat != null),
72       _fullYearFormat = fullYearFormat,
73       assert(dayFormat != null),
74       _dayFormat = dayFormat,
75       assert(mediumDateFormat != null),
76       _mediumDateFormat = mediumDateFormat,
77       assert(singleDigitHourFormat != null),
78       _singleDigitHourFormat = singleDigitHourFormat,
79       assert(singleDigitMinuteFormat != null),
80       _singleDigitMinuteFormat = singleDigitMinuteFormat,
81       assert(doubleDigitMinuteFormat != null),
82       _doubleDigitMinuteFormat = doubleDigitMinuteFormat,
83       assert(singleDigitSecondFormat != null),
84       _singleDigitSecondFormat = singleDigitSecondFormat,
85       assert(decimalFormat != null),
86       _decimalFormat =decimalFormat;
87
88  final String _localeName;
89  final intl.DateFormat _fullYearFormat;
90  final intl.DateFormat _dayFormat;
91  final intl.DateFormat _mediumDateFormat;
92  final intl.DateFormat _singleDigitHourFormat;
93  final intl.DateFormat _singleDigitMinuteFormat;
94  final intl.DateFormat _doubleDigitMinuteFormat;
95  final intl.DateFormat _singleDigitSecondFormat;
96  final intl.NumberFormat _decimalFormat;
97
98  @override
99  String datePickerYear(int yearIndex) {
100    return _fullYearFormat.format(DateTime.utc(yearIndex));
101  }
102
103  @override
104  String datePickerMonth(int monthIndex) {
105    // It doesn't actually have anything to do with _fullYearFormat. It's just
106    // taking advantage of the fact that _fullYearFormat loaded the needed
107    // locale's symbols.
108    return _fullYearFormat.dateSymbols.MONTHS[monthIndex - 1];
109  }
110
111  @override
112  String datePickerDayOfMonth(int dayIndex) {
113    // Year and month doesn't matter since we just want to day formatted.
114    return _dayFormat.format(DateTime.utc(0, 0, dayIndex));
115  }
116
117  @override
118  String datePickerMediumDate(DateTime date) {
119    return _mediumDateFormat.format(date);
120  }
121
122  @override
123  String datePickerHour(int hour) {
124    return _singleDigitHourFormat.format(DateTime.utc(0, 0, 0, hour));
125  }
126
127  @override
128  String datePickerMinute(int minute) {
129    return _doubleDigitMinuteFormat.format(DateTime.utc(0, 0, 0, 0, minute));
130  }
131
132  /// Subclasses should provide the optional zero pluralization of [datePickerHourSemanticsLabel] based on the ARB file.
133  @protected String get datePickerHourSemanticsLabelZero => null;
134  /// Subclasses should provide the optional one pluralization of [datePickerHourSemanticsLabel] based on the ARB file.
135  @protected String get datePickerHourSemanticsLabelOne => null;
136  /// Subclasses should provide the optional two pluralization of [datePickerHourSemanticsLabel] based on the ARB file.
137  @protected String get datePickerHourSemanticsLabelTwo => null;
138  /// Subclasses should provide the optional few pluralization of [datePickerHourSemanticsLabel] based on the ARB file.
139  @protected String get datePickerHourSemanticsLabelFew => null;
140  /// Subclasses should provide the optional many pluralization of [datePickerHourSemanticsLabel] based on the ARB file.
141  @protected String get datePickerHourSemanticsLabelMany => null;
142  /// Subclasses should provide the required other pluralization of [datePickerHourSemanticsLabel] based on the ARB file.
143  @protected String get datePickerHourSemanticsLabelOther;
144
145  @override
146  String datePickerHourSemanticsLabel(int hour) {
147    return intl.Intl.pluralLogic(
148      hour,
149      zero: datePickerHourSemanticsLabelZero,
150      one: datePickerHourSemanticsLabelOne,
151      two: datePickerHourSemanticsLabelTwo,
152      few: datePickerHourSemanticsLabelFew,
153      many: datePickerHourSemanticsLabelMany,
154      other: datePickerHourSemanticsLabelOther,
155      locale: _localeName,
156    ).replaceFirst(r'$hour', _decimalFormat.format(hour));
157  }
158
159  /// Subclasses should provide the optional zero pluralization of [datePickerMinuteSemanticsLabel] based on the ARB file.
160  @protected String get datePickerMinuteSemanticsLabelZero => null;
161  /// Subclasses should provide the optional one pluralization of [datePickerMinuteSemanticsLabel] based on the ARB file.
162  @protected String get datePickerMinuteSemanticsLabelOne => null;
163  /// Subclasses should provide the optional two pluralization of [datePickerMinuteSemanticsLabel] based on the ARB file.
164  @protected String get datePickerMinuteSemanticsLabelTwo => null;
165  /// Subclasses should provide the optional few pluralization of [datePickerMinuteSemanticsLabel] based on the ARB file.
166  @protected String get datePickerMinuteSemanticsLabelFew => null;
167  /// Subclasses should provide the optional many pluralization of [datePickerMinuteSemanticsLabel] based on the ARB file.
168  @protected String get datePickerMinuteSemanticsLabelMany => null;
169  /// Subclasses should provide the required other pluralization of [datePickerMinuteSemanticsLabel] based on the ARB file.
170  @protected String get datePickerMinuteSemanticsLabelOther;
171
172  @override
173  String datePickerMinuteSemanticsLabel(int minute) {
174    return intl.Intl.pluralLogic(
175      minute,
176      zero: datePickerMinuteSemanticsLabelZero,
177      one: datePickerMinuteSemanticsLabelOne,
178      two: datePickerMinuteSemanticsLabelTwo,
179      few: datePickerMinuteSemanticsLabelFew,
180      many: datePickerMinuteSemanticsLabelMany,
181      other: datePickerMinuteSemanticsLabelOther,
182      locale: _localeName,
183    ).replaceFirst(r'$minute', _decimalFormat.format(minute));
184  }
185
186  /// A string describing the [DatePickerDateOrder] enum value.
187  ///
188  /// Subclasses should provide this string value based on the ARB file for
189  /// the locale.
190  ///
191  /// See also:
192  ///
193  ///  * [datePickerDateOrder], which provides the [DatePickerDateOrder]
194  ///    enum value for [CupertinoLocalizations] based on this string value
195  @protected
196  String get datePickerDateOrderString;
197
198  @override
199  DatePickerDateOrder get datePickerDateOrder {
200    switch (datePickerDateOrderString) {
201      case 'dmy':
202        return DatePickerDateOrder.dmy;
203      case 'mdy':
204        return DatePickerDateOrder.mdy;
205      case 'ymd':
206        return DatePickerDateOrder.ymd;
207      case 'ydm':
208        return DatePickerDateOrder.ydm;
209      default:
210        assert(
211          false,
212          'Failed to load DatePickerDateOrder $datePickerDateOrderString for '
213          'locale $_localeName.\nNon conforming string for $_localeName\'s '
214          '.arb file',
215        );
216        return null;
217    }
218  }
219
220  /// A string describing the [DatePickerDateTimeOrder] enum value.
221  ///
222  /// Subclasses should provide this string value based on the ARB file for
223  /// the locale.
224  ///
225  /// See also:
226  ///
227  ///  * [datePickerDateTimeOrder], which provides the [DatePickerDateTimeOrder]
228  ///    enum value for [CupertinoLocalizations] based on this string value.
229  @protected
230  String get datePickerDateTimeOrderString;
231
232  @override
233  DatePickerDateTimeOrder get datePickerDateTimeOrder {
234    switch (datePickerDateTimeOrderString) {
235      case 'date_time_dayPeriod':
236        return DatePickerDateTimeOrder.date_time_dayPeriod;
237      case 'date_dayPeriod_time':
238        return DatePickerDateTimeOrder.date_dayPeriod_time;
239      case 'time_dayPeriod_date':
240        return DatePickerDateTimeOrder.time_dayPeriod_date;
241      case 'dayPeriod_time_date':
242        return DatePickerDateTimeOrder.dayPeriod_time_date;
243      default:
244        assert(
245          false,
246          'Failed to load DatePickerDateTimeOrder $datePickerDateTimeOrderString '
247          'for locale $_localeName.\nNon conforming string for $_localeName\'s '
248          '.arb file',
249        );
250        return null;
251    }
252  }
253
254  @override
255  String timerPickerHour(int hour) {
256    return _singleDigitHourFormat.format(DateTime.utc(0, 0, 0, hour));
257  }
258
259  @override
260  String timerPickerMinute(int minute) {
261    return _singleDigitMinuteFormat.format(DateTime.utc(0, 0, 0, 0, minute));
262  }
263
264  @override
265  String timerPickerSecond(int second) {
266    return _singleDigitSecondFormat.format(DateTime.utc(0, 0, 0, 0, 0, second));
267  }
268
269  /// Subclasses should provide the optional zero pluralization of [timerPickerHourLabel] based on the ARB file.
270  @protected String get timerPickerHourLabelZero => null;
271  /// Subclasses should provide the optional one pluralization of [timerPickerHourLabel] based on the ARB file.
272  @protected String get timerPickerHourLabelOne => null;
273  /// Subclasses should provide the optional two pluralization of [timerPickerHourLabel] based on the ARB file.
274  @protected String get timerPickerHourLabelTwo => null;
275  /// Subclasses should provide the optional few pluralization of [timerPickerHourLabel] based on the ARB file.
276  @protected String get timerPickerHourLabelFew => null;
277  /// Subclasses should provide the optional many pluralization of [timerPickerHourLabel] based on the ARB file.
278  @protected String get timerPickerHourLabelMany => null;
279  /// Subclasses should provide the required other pluralization of [timerPickerHourLabel] based on the ARB file.
280  @protected String get timerPickerHourLabelOther;
281
282  @override
283  String timerPickerHourLabel(int hour) {
284    return intl.Intl.pluralLogic(
285      hour,
286      zero: timerPickerHourLabelZero,
287      one: timerPickerHourLabelOne,
288      two: timerPickerHourLabelTwo,
289      few: timerPickerHourLabelFew,
290      many: timerPickerHourLabelMany,
291      other: timerPickerHourLabelOther,
292      locale: _localeName,
293    ).replaceFirst(r'$hour', _decimalFormat.format(hour));
294  }
295
296  /// Subclasses should provide the optional zero pluralization of [timerPickerMinuteLabel] based on the ARB file.
297  @protected String get timerPickerMinuteLabelZero => null;
298  /// Subclasses should provide the optional one pluralization of [timerPickerMinuteLabel] based on the ARB file.
299  @protected String get timerPickerMinuteLabelOne => null;
300  /// Subclasses should provide the optional two pluralization of [timerPickerMinuteLabel] based on the ARB file.
301  @protected String get timerPickerMinuteLabelTwo => null;
302  /// Subclasses should provide the optional few pluralization of [timerPickerMinuteLabel] based on the ARB file.
303  @protected String get timerPickerMinuteLabelFew => null;
304  /// Subclasses should provide the optional many pluralization of [timerPickerMinuteLabel] based on the ARB file.
305  @protected String get timerPickerMinuteLabelMany => null;
306  /// Subclasses should provide the required other pluralization of [timerPickerMinuteLabel] based on the ARB file.
307  @protected String get timerPickerMinuteLabelOther;
308
309  @override
310  String timerPickerMinuteLabel(int minute) {
311    return intl.Intl.pluralLogic(
312      minute,
313      zero: timerPickerMinuteLabelZero,
314      one: timerPickerMinuteLabelOne,
315      two: timerPickerMinuteLabelTwo,
316      few: timerPickerMinuteLabelFew,
317      many: timerPickerMinuteLabelMany,
318      other: timerPickerMinuteLabelOther,
319      locale: _localeName,
320    ).replaceFirst(r'$minute', _decimalFormat.format(minute));
321  }
322
323  /// Subclasses should provide the optional zero pluralization of [timerPickerSecondLabel] based on the ARB file.
324  @protected String get timerPickerSecondLabelZero => null;
325  /// Subclasses should provide the optional one pluralization of [timerPickerSecondLabel] based on the ARB file.
326  @protected String get timerPickerSecondLabelOne => null;
327  /// Subclasses should provide the optional two pluralization of [timerPickerSecondLabel] based on the ARB file.
328  @protected String get timerPickerSecondLabelTwo => null;
329  /// Subclasses should provide the optional few pluralization of [timerPickerSecondLabel] based on the ARB file.
330  @protected String get timerPickerSecondLabelFew => null;
331  /// Subclasses should provide the optional many pluralization of [timerPickerSecondLabel] based on the ARB file.
332  @protected String get timerPickerSecondLabelMany => null;
333  /// Subclasses should provide the required other pluralization of [timerPickerSecondLabel] based on the ARB file.
334  @protected String get timerPickerSecondLabelOther;
335
336  @override
337  String timerPickerSecondLabel(int second) {
338    return intl.Intl.pluralLogic(
339      second,
340      zero: timerPickerSecondLabelZero,
341      one: timerPickerSecondLabelOne,
342      two: timerPickerSecondLabelTwo,
343      few: timerPickerSecondLabelFew,
344      many: timerPickerSecondLabelMany,
345      other: timerPickerSecondLabelOther,
346      locale: _localeName,
347    ).replaceFirst(r'$second', _decimalFormat.format(second));
348  }
349
350  /// A [LocalizationsDelegate] that uses [GlobalCupertinoLocalizations.load]
351  /// to create an instance of this class.
352  ///
353  /// Most internationalized apps will use [GlobalCupertinoLocalizations.delegates]
354  /// as the value of [CupertinoApp.localizationsDelegates] to include
355  /// the localizations for both the cupertino and widget libraries.
356  static const LocalizationsDelegate<CupertinoLocalizations> delegate = _GlobalCupertinoLocalizationsDelegate();
357
358  /// A value for [CupertinoApp.localizationsDelegates] that's typically used by
359  /// internationalized apps.
360  ///
361  /// ## Sample code
362  ///
363  /// To include the localizations provided by this class and by
364  /// [GlobalWidgetsLocalizations] in a [CupertinoApp],
365  /// use [GlobalCupertinoLocalizations.delegates] as the value of
366  /// [CupertinoApp.localizationsDelegates], and specify the locales your
367  /// app supports with [CupertinoApp.supportedLocales]:
368  ///
369  /// ```dart
370  /// new CupertinoApp(
371  ///   localizationsDelegates: GlobalCupertinoLocalizations.delegates,
372  ///   supportedLocales: [
373  ///     const Locale('en', 'US'), // English
374  ///     const Locale('he', 'IL'), // Hebrew
375  ///   ],
376  ///   // ...
377  /// )
378  /// ```
379  static const List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[
380    GlobalCupertinoLocalizations.delegate,
381    GlobalWidgetsLocalizations.delegate,
382  ];
383}
384
385class _GlobalCupertinoLocalizationsDelegate extends LocalizationsDelegate<CupertinoLocalizations> {
386  const _GlobalCupertinoLocalizationsDelegate();
387
388  @override
389  bool isSupported(Locale locale) => kCupertinoSupportedLanguages.contains(locale.languageCode);
390
391  static final Map<Locale, Future<CupertinoLocalizations>> _loadedTranslations = <Locale, Future<CupertinoLocalizations>>{};
392
393  @override
394  Future<CupertinoLocalizations> load(Locale locale) {
395    assert(isSupported(locale));
396    return _loadedTranslations.putIfAbsent(locale, () {
397      util.loadDateIntlDataIfNotLoaded();
398
399      final String localeName = intl.Intl.canonicalizedLocale(locale.toString());
400      assert(
401        locale.toString() == localeName,
402        'Flutter does not support the non-standard locale form $locale (which '
403        'might be $localeName',
404      );
405
406      intl.DateFormat fullYearFormat;
407      intl.DateFormat dayFormat;
408      intl.DateFormat mediumDateFormat;
409      // We don't want any additional decoration here. The am/pm is handled in
410      // the date picker. We just want an hour number localized.
411      intl.DateFormat singleDigitHourFormat;
412      intl.DateFormat singleDigitMinuteFormat;
413      intl.DateFormat doubleDigitMinuteFormat;
414      intl.DateFormat singleDigitSecondFormat;
415      intl.NumberFormat decimalFormat;
416
417      void loadFormats(String locale) {
418        fullYearFormat = intl.DateFormat.y(locale);
419        dayFormat = intl.DateFormat.d(locale);
420        mediumDateFormat = intl.DateFormat.MMMEd(locale);
421        // TODO(xster): fix when https://github.com/dart-lang/intl/issues/207 is resolved.
422        singleDigitHourFormat = intl.DateFormat('HH', locale);
423        singleDigitMinuteFormat = intl.DateFormat.m(locale);
424        doubleDigitMinuteFormat = intl.DateFormat('mm', locale);
425        singleDigitSecondFormat = intl.DateFormat.s(locale);
426        decimalFormat = intl.NumberFormat.decimalPattern(locale);
427      }
428
429      if (intl.DateFormat.localeExists(localeName)) {
430        loadFormats(localeName);
431      } else if (intl.DateFormat.localeExists(locale.languageCode)) {
432        loadFormats(locale.languageCode);
433      } else {
434        loadFormats(null);
435      }
436
437      return SynchronousFuture<CupertinoLocalizations>(getCupertinoTranslation(
438        locale,
439        fullYearFormat,
440        dayFormat,
441        mediumDateFormat,
442        singleDigitHourFormat,
443        singleDigitMinuteFormat,
444        doubleDigitMinuteFormat,
445        singleDigitSecondFormat,
446        decimalFormat,
447      ));
448    });
449  }
450
451  @override
452  bool shouldReload(_GlobalCupertinoLocalizationsDelegate old) => false;
453
454  @override
455  String toString() => 'GlobalCupertinoLocalizations.delegate(${kCupertinoSupportedLanguages.length} locales)';
456}
457