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