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