xster | e2b6a3a | 2019-04-08 16:56:58 -0700 | [diff] [blame^] | 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 | |
| 5 | import 'dart:async'; |
| 6 | |
| 7 | import 'package:flutter/cupertino.dart'; |
| 8 | import 'package:flutter/foundation.dart'; |
| 9 | import 'package:intl/intl.dart' as intl; |
| 10 | import 'package:intl/date_symbols.dart' as intl; |
| 11 | |
| 12 | import 'utils/date_localizations.dart' as util; |
| 13 | import '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. |
| 25 | abstract 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 | |
| 357 | class _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 | |
| 427 | CupertinoLocalizations _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 | } |