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