805 lines
29 KiB
Dart
805 lines
29 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:intl/intl.dart' as intl;
|
|
|
|
import 'cupertino_localizations.dart';
|
|
import 'l10n/generated_material_localizations.dart';
|
|
import 'utils/date_localizations.dart' as util;
|
|
import 'widgets_localizations.dart';
|
|
|
|
/// Implementation of localized strings for the material widgets using the
|
|
/// `intl` package for date and time formatting.
|
|
///
|
|
/// ## Supported languages
|
|
///
|
|
/// This class supports locales with the following [Locale.languageCode]s:
|
|
///
|
|
/// {@macro flutter.localizations.material.languages}
|
|
///
|
|
/// This list is available programmatically via [kMaterialSupportedLanguages].
|
|
///
|
|
/// ## Sample code
|
|
///
|
|
/// To include the localizations provided by this class in a [MaterialApp],
|
|
/// add [GlobalMaterialLocalizations.delegates] to
|
|
/// [MaterialApp.localizationsDelegates], and specify the locales your
|
|
/// app supports with [MaterialApp.supportedLocales]:
|
|
///
|
|
/// ```dart
|
|
/// MaterialApp(
|
|
/// localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
|
/// supportedLocales: [
|
|
/// const Locale('en', 'US'), // American English
|
|
/// const Locale('he', 'IL'), // Israeli Hebrew
|
|
/// // ...
|
|
/// ],
|
|
/// // ...
|
|
/// )
|
|
/// ```
|
|
///
|
|
/// ## Overriding translations
|
|
///
|
|
/// To create a translation that's similar to an existing language's translation
|
|
/// but has slightly different strings, subclass the relevant translation
|
|
/// directly and then create a [LocalizationsDelegate<MaterialLocalizations>]
|
|
/// subclass to define how to load it.
|
|
///
|
|
/// Avoid subclassing an unrelated language (for example, subclassing
|
|
/// [MaterialLocalizationEn] and then passing a non-English `localeName` to the
|
|
/// constructor). Doing so will cause confusion for locale-specific behaviors;
|
|
/// in particular, translations that use the `localeName` for determining how to
|
|
/// pluralize will end up doing invalid things. Subclassing an existing
|
|
/// language's translations is only suitable for making small changes to the
|
|
/// existing strings. For providing a new language entirely, implement
|
|
/// [MaterialLocalizations] directly.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * The Flutter Internationalization Tutorial,
|
|
/// <https://flutter.dev/tutorials/internationalization/>.
|
|
/// * [DefaultMaterialLocalizations], which only provides US English translations.
|
|
abstract class GlobalMaterialLocalizations implements MaterialLocalizations {
|
|
/// Initializes an object that defines the material widgets' localized strings
|
|
/// for the given `locale`.
|
|
///
|
|
/// The arguments are used for further runtime localization of data,
|
|
/// specifically for selecting plurals, date and time formatting, and number
|
|
/// formatting. They correspond to the following values:
|
|
///
|
|
/// 1. The string that would be returned by [Intl.canonicalizedLocale] for
|
|
/// the locale.
|
|
/// 2. The [DateFormat] for [formatYear].
|
|
/// 3. The [DateFormat] for [formatShortDate].
|
|
/// 4. The [DateFormat] for [formatMediumDate].
|
|
/// 5. The [DateFormat] for [formatFullDate].
|
|
/// 6. The [DateFormat] for [formatMonthYear].
|
|
/// 7. The [DateFormat] for [formatShortMonthDay].
|
|
/// 8. The [NumberFormat] for [formatDecimal] (also used by [formatHour] and
|
|
/// [formatTimeOfDay] when [timeOfDayFormat] doesn't use [HourFormat.HH]).
|
|
/// 9. The [NumberFormat] for [formatHour] and the hour part of
|
|
/// [formatTimeOfDay] when [timeOfDayFormat] uses [HourFormat.HH], and for
|
|
/// [formatMinute] and the minute part of [formatTimeOfDay].
|
|
///
|
|
/// The [narrowWeekdays] and [firstDayOfWeekIndex] properties use the values
|
|
/// from the [intl.DateFormat] used by [formatFullDate].
|
|
const GlobalMaterialLocalizations({
|
|
required String localeName,
|
|
required intl.DateFormat fullYearFormat,
|
|
required intl.DateFormat compactDateFormat,
|
|
required intl.DateFormat shortDateFormat,
|
|
required intl.DateFormat mediumDateFormat,
|
|
required intl.DateFormat longDateFormat,
|
|
required intl.DateFormat yearMonthFormat,
|
|
required intl.DateFormat shortMonthDayFormat,
|
|
required intl.NumberFormat decimalFormat,
|
|
required intl.NumberFormat twoDigitZeroPaddedFormat,
|
|
}) : assert(localeName != null),
|
|
_localeName = localeName,
|
|
assert(fullYearFormat != null),
|
|
_fullYearFormat = fullYearFormat,
|
|
assert(compactDateFormat != null),
|
|
_compactDateFormat = compactDateFormat,
|
|
assert(shortDateFormat != null),
|
|
_shortDateFormat = shortDateFormat,
|
|
assert(mediumDateFormat != null),
|
|
_mediumDateFormat = mediumDateFormat,
|
|
assert(longDateFormat != null),
|
|
_longDateFormat = longDateFormat,
|
|
assert(yearMonthFormat != null),
|
|
_yearMonthFormat = yearMonthFormat,
|
|
assert(shortMonthDayFormat != null),
|
|
_shortMonthDayFormat = shortMonthDayFormat,
|
|
assert(decimalFormat != null),
|
|
_decimalFormat = decimalFormat,
|
|
assert(twoDigitZeroPaddedFormat != null),
|
|
_twoDigitZeroPaddedFormat = twoDigitZeroPaddedFormat;
|
|
|
|
final String _localeName;
|
|
final intl.DateFormat _fullYearFormat;
|
|
final intl.DateFormat _compactDateFormat;
|
|
final intl.DateFormat _shortDateFormat;
|
|
final intl.DateFormat _mediumDateFormat;
|
|
final intl.DateFormat _longDateFormat;
|
|
final intl.DateFormat _yearMonthFormat;
|
|
final intl.DateFormat _shortMonthDayFormat;
|
|
final intl.NumberFormat _decimalFormat;
|
|
final intl.NumberFormat _twoDigitZeroPaddedFormat;
|
|
|
|
@override
|
|
String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat = false }) {
|
|
switch (hourFormat(of: timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat))) {
|
|
case HourFormat.HH:
|
|
return _twoDigitZeroPaddedFormat.format(timeOfDay.hour);
|
|
case HourFormat.H:
|
|
return formatDecimal(timeOfDay.hour);
|
|
case HourFormat.h:
|
|
final int hour = timeOfDay.hourOfPeriod;
|
|
return formatDecimal(hour == 0 ? 12 : hour);
|
|
}
|
|
}
|
|
|
|
@override
|
|
String formatMinute(TimeOfDay timeOfDay) {
|
|
return _twoDigitZeroPaddedFormat.format(timeOfDay.minute);
|
|
}
|
|
|
|
@override
|
|
String formatYear(DateTime date) {
|
|
return _fullYearFormat.format(date);
|
|
}
|
|
|
|
@override
|
|
String formatCompactDate(DateTime date) {
|
|
return _compactDateFormat.format(date);
|
|
}
|
|
|
|
@override
|
|
String formatShortDate(DateTime date) {
|
|
return _shortDateFormat.format(date);
|
|
}
|
|
|
|
@override
|
|
String formatMediumDate(DateTime date) {
|
|
return _mediumDateFormat.format(date);
|
|
}
|
|
|
|
@override
|
|
String formatFullDate(DateTime date) {
|
|
return _longDateFormat.format(date);
|
|
}
|
|
|
|
@override
|
|
String formatMonthYear(DateTime date) {
|
|
return _yearMonthFormat.format(date);
|
|
}
|
|
|
|
@override
|
|
String formatShortMonthDay(DateTime date) {
|
|
return _shortMonthDayFormat.format(date);
|
|
}
|
|
|
|
@override
|
|
DateTime? parseCompactDate(String? inputString) {
|
|
try {
|
|
return inputString != null ? _compactDateFormat.parseStrict(inputString) : null;
|
|
} on FormatException {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@override
|
|
List<String> get narrowWeekdays {
|
|
return _longDateFormat.dateSymbols.NARROWWEEKDAYS;
|
|
}
|
|
|
|
@override
|
|
int get firstDayOfWeekIndex => (_longDateFormat.dateSymbols.FIRSTDAYOFWEEK + 1) % 7;
|
|
|
|
@override
|
|
String formatDecimal(int number) {
|
|
return _decimalFormat.format(number);
|
|
}
|
|
|
|
@override
|
|
String formatTimeOfDay(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat = false }) {
|
|
// Not using intl.DateFormat for two reasons:
|
|
//
|
|
// - DateFormat supports more formats than our material time picker does,
|
|
// and we want to be consistent across time picker format and the string
|
|
// formatting of the time of day.
|
|
// - DateFormat operates on DateTime, which is sensitive to time eras and
|
|
// time zones, while here we want to format hour and minute within one day
|
|
// no matter what date the day falls on.
|
|
final String hour = formatHour(timeOfDay, alwaysUse24HourFormat: alwaysUse24HourFormat);
|
|
final String minute = formatMinute(timeOfDay);
|
|
switch (timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat)) {
|
|
case TimeOfDayFormat.h_colon_mm_space_a:
|
|
return '$hour:$minute ${_formatDayPeriod(timeOfDay)!}';
|
|
case TimeOfDayFormat.H_colon_mm:
|
|
case TimeOfDayFormat.HH_colon_mm:
|
|
return '$hour:$minute';
|
|
case TimeOfDayFormat.HH_dot_mm:
|
|
return '$hour.$minute';
|
|
case TimeOfDayFormat.a_space_h_colon_mm:
|
|
return '${_formatDayPeriod(timeOfDay)!} $hour:$minute';
|
|
case TimeOfDayFormat.frenchCanadian:
|
|
return '$hour h $minute';
|
|
}
|
|
}
|
|
|
|
String? _formatDayPeriod(TimeOfDay timeOfDay) {
|
|
switch (timeOfDay.period) {
|
|
case DayPeriod.am:
|
|
return anteMeridiemAbbreviation;
|
|
case DayPeriod.pm:
|
|
return postMeridiemAbbreviation;
|
|
}
|
|
}
|
|
|
|
/// The raw version of [dateRangeStartDateSemanticLabel], with `$formattedDate` verbatim
|
|
/// in the string.
|
|
@protected
|
|
String get dateRangeStartDateSemanticLabelRaw;
|
|
|
|
@override
|
|
String dateRangeStartDateSemanticLabel(String formattedDate) {
|
|
return dateRangeStartDateSemanticLabelRaw.replaceFirst(r'$fullDate', formattedDate);
|
|
}
|
|
|
|
/// The raw version of [dateRangeEndDateSemanticLabel], with `$fullDate` verbatim
|
|
/// in the string.
|
|
@protected
|
|
String get dateRangeEndDateSemanticLabelRaw;
|
|
|
|
@override
|
|
String dateRangeEndDateSemanticLabel(String formattedDate) {
|
|
return dateRangeEndDateSemanticLabelRaw.replaceFirst(r'$fullDate', formattedDate);
|
|
}
|
|
|
|
/// The raw version of [aboutListTileTitle], with `$applicationName` verbatim
|
|
/// in the string.
|
|
@protected
|
|
String get aboutListTileTitleRaw;
|
|
|
|
@override
|
|
String aboutListTileTitle(String applicationName) {
|
|
final String text = aboutListTileTitleRaw;
|
|
return text.replaceFirst(r'$applicationName', applicationName);
|
|
}
|
|
|
|
/// The raw version of [pageRowsInfoTitle], with `$firstRow`, `$lastRow`' and
|
|
/// `$rowCount` verbatim in the string, for the case where the value is
|
|
/// approximate.
|
|
@protected
|
|
String get pageRowsInfoTitleApproximateRaw;
|
|
|
|
/// The raw version of [pageRowsInfoTitle], with `$firstRow`, `$lastRow`' and
|
|
/// `$rowCount` verbatim in the string, for the case where the value is
|
|
/// precise.
|
|
@protected
|
|
String get pageRowsInfoTitleRaw;
|
|
|
|
@override
|
|
String pageRowsInfoTitle(int firstRow, int lastRow, int rowCount, bool rowCountIsApproximate) {
|
|
String? text = rowCountIsApproximate ? pageRowsInfoTitleApproximateRaw : null;
|
|
text ??= pageRowsInfoTitleRaw;
|
|
assert(text != null, 'A $_localeName localization was not found for pageRowsInfoTitle or pageRowsInfoTitleApproximate');
|
|
return text
|
|
.replaceFirst(r'$firstRow', formatDecimal(firstRow))
|
|
.replaceFirst(r'$lastRow', formatDecimal(lastRow))
|
|
.replaceFirst(r'$rowCount', formatDecimal(rowCount));
|
|
}
|
|
|
|
/// The raw version of [tabLabel], with `$tabIndex` and `$tabCount` verbatim
|
|
/// in the string.
|
|
@protected
|
|
String get tabLabelRaw;
|
|
|
|
@override
|
|
String tabLabel({ required int tabIndex, required int tabCount }) {
|
|
assert(tabIndex >= 1);
|
|
assert(tabCount >= 1);
|
|
final String template = tabLabelRaw;
|
|
return template
|
|
.replaceFirst(r'$tabIndex', formatDecimal(tabIndex))
|
|
.replaceFirst(r'$tabCount', formatDecimal(tabCount));
|
|
}
|
|
|
|
/// The "zero" form of [selectedRowCountTitle].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [selectedRowCountTitleOne], the "one" form
|
|
/// * [selectedRowCountTitleTwo], the "two" form
|
|
/// * [selectedRowCountTitleFew], the "few" form
|
|
/// * [selectedRowCountTitleMany], the "many" form
|
|
/// * [selectedRowCountTitleOther], the "other" form
|
|
@protected
|
|
String? get selectedRowCountTitleZero => null;
|
|
|
|
/// The "one" form of [selectedRowCountTitle].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [selectedRowCountTitleZero], the "zero" form
|
|
/// * [selectedRowCountTitleTwo], the "two" form
|
|
/// * [selectedRowCountTitleFew], the "few" form
|
|
/// * [selectedRowCountTitleMany], the "many" form
|
|
/// * [selectedRowCountTitleOther], the "other" form
|
|
@protected
|
|
String? get selectedRowCountTitleOne => null;
|
|
|
|
/// The "two" form of [selectedRowCountTitle].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [selectedRowCountTitleZero], the "zero" form
|
|
/// * [selectedRowCountTitleOne], the "one" form
|
|
/// * [selectedRowCountTitleFew], the "few" form
|
|
/// * [selectedRowCountTitleMany], the "many" form
|
|
/// * [selectedRowCountTitleOther], the "other" form
|
|
@protected
|
|
String? get selectedRowCountTitleTwo => null;
|
|
|
|
/// The "few" form of [selectedRowCountTitle].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [selectedRowCountTitleZero], the "zero" form
|
|
/// * [selectedRowCountTitleOne], the "one" form
|
|
/// * [selectedRowCountTitleTwo], the "two" form
|
|
/// * [selectedRowCountTitleMany], the "many" form
|
|
/// * [selectedRowCountTitleOther], the "other" form
|
|
@protected
|
|
String? get selectedRowCountTitleFew => null;
|
|
|
|
/// The "many" form of [selectedRowCountTitle].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [selectedRowCountTitleZero], the "zero" form
|
|
/// * [selectedRowCountTitleOne], the "one" form
|
|
/// * [selectedRowCountTitleTwo], the "two" form
|
|
/// * [selectedRowCountTitleFew], the "few" form
|
|
/// * [selectedRowCountTitleOther], the "other" form
|
|
@protected
|
|
String? get selectedRowCountTitleMany => null;
|
|
|
|
/// The "other" form of [selectedRowCountTitle].
|
|
///
|
|
/// This form is required.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [selectedRowCountTitleZero], the "zero" form
|
|
/// * [selectedRowCountTitleOne], the "one" form
|
|
/// * [selectedRowCountTitleTwo], the "two" form
|
|
/// * [selectedRowCountTitleFew], the "few" form
|
|
/// * [selectedRowCountTitleMany], the "many" form
|
|
@protected
|
|
String get selectedRowCountTitleOther;
|
|
|
|
@override
|
|
String selectedRowCountTitle(int selectedRowCount) {
|
|
return intl.Intl.pluralLogic(
|
|
selectedRowCount,
|
|
zero: selectedRowCountTitleZero,
|
|
one: selectedRowCountTitleOne,
|
|
two: selectedRowCountTitleTwo,
|
|
few: selectedRowCountTitleFew,
|
|
many: selectedRowCountTitleMany,
|
|
other: selectedRowCountTitleOther,
|
|
locale: _localeName,
|
|
).replaceFirst(r'$selectedRowCount', formatDecimal(selectedRowCount));
|
|
}
|
|
|
|
/// The format to use for [timeOfDayFormat].
|
|
@protected
|
|
TimeOfDayFormat get timeOfDayFormatRaw;
|
|
|
|
/// The [TimeOfDayFormat] corresponding to one of the following supported
|
|
/// patterns:
|
|
///
|
|
/// * `HH:mm`
|
|
/// * `HH.mm`
|
|
/// * `HH 'h' mm`
|
|
/// * `HH:mm น.`
|
|
/// * `H:mm`
|
|
/// * `h:mm a`
|
|
/// * `a h:mm`
|
|
/// * `ah:mm`
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * <http://demo.icu-project.org/icu-bin/locexp?d_=en&_=en_US>, which shows
|
|
/// the short time pattern used in the `en_US` locale.
|
|
@override
|
|
TimeOfDayFormat timeOfDayFormat({ bool alwaysUse24HourFormat = false }) {
|
|
assert(alwaysUse24HourFormat != null);
|
|
if (alwaysUse24HourFormat) {
|
|
return _get24HourVersionOf(timeOfDayFormatRaw);
|
|
}
|
|
return timeOfDayFormatRaw;
|
|
}
|
|
|
|
/// The "zero" form of [licensesPackageDetailText].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [licensesPackageDetailTextZero], the "zero" form
|
|
/// * [licensesPackageDetailTextOne], the "one" form
|
|
/// * [licensesPackageDetailTextTwo], the "two" form
|
|
/// * [licensesPackageDetailTextFew], the "few" form
|
|
/// * [licensesPackageDetailTextMany], the "many" form
|
|
/// * [licensesPackageDetailTextOther], the "other" form
|
|
@protected
|
|
String? get licensesPackageDetailTextZero => null;
|
|
|
|
/// The "one" form of [licensesPackageDetailText].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [licensesPackageDetailTextZero], the "zero" form
|
|
/// * [licensesPackageDetailTextOne], the "one" form
|
|
/// * [licensesPackageDetailTextTwo], the "two" form
|
|
/// * [licensesPackageDetailTextFew], the "few" form
|
|
/// * [licensesPackageDetailTextMany], the "many" form
|
|
/// * [licensesPackageDetailTextOther], the "other" form
|
|
@protected
|
|
String? get licensesPackageDetailTextOne => null;
|
|
|
|
/// The "two" form of [licensesPackageDetailText].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [licensesPackageDetailTextZero], the "zero" form
|
|
/// * [licensesPackageDetailTextOne], the "one" form
|
|
/// * [licensesPackageDetailTextTwo], the "two" form
|
|
/// * [licensesPackageDetailTextFew], the "few" form
|
|
/// * [licensesPackageDetailTextMany], the "many" form
|
|
/// * [licensesPackageDetailTextOther], the "other" form
|
|
@protected
|
|
String? get licensesPackageDetailTextTwo => null;
|
|
|
|
/// The "many" form of [licensesPackageDetailText].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [licensesPackageDetailTextZero], the "zero" form
|
|
/// * [licensesPackageDetailTextOne], the "one" form
|
|
/// * [licensesPackageDetailTextTwo], the "two" form
|
|
/// * [licensesPackageDetailTextFew], the "few" form
|
|
/// * [licensesPackageDetailTextMany], the "many" form
|
|
/// * [licensesPackageDetailTextOther], the "other" form
|
|
@protected
|
|
String? get licensesPackageDetailTextMany => null;
|
|
|
|
/// The "few" form of [licensesPackageDetailText].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [licensesPackageDetailTextZero], the "zero" form
|
|
/// * [licensesPackageDetailTextOne], the "one" form
|
|
/// * [licensesPackageDetailTextTwo], the "two" form
|
|
/// * [licensesPackageDetailTextFew], the "few" form
|
|
/// * [licensesPackageDetailTextMany], the "many" form
|
|
/// * [licensesPackageDetailTextOther], the "other" form
|
|
@protected
|
|
String? get licensesPackageDetailTextFew => null;
|
|
|
|
/// The "other" form of [licensesPackageDetailText].
|
|
///
|
|
/// This form is required.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [licensesPackageDetailTextZero], the "zero" form
|
|
/// * [licensesPackageDetailTextOne], the "one" form
|
|
/// * [licensesPackageDetailTextTwo], the "two" form
|
|
/// * [licensesPackageDetailTextFew], the "few" form
|
|
/// * [licensesPackageDetailTextMany], the "many" form
|
|
/// * [licensesPackageDetailTextOther], the "other" form
|
|
@protected
|
|
String get licensesPackageDetailTextOther;
|
|
|
|
@override
|
|
String licensesPackageDetailText(int licenseCount) {
|
|
return intl.Intl.pluralLogic(
|
|
licenseCount,
|
|
zero: licensesPackageDetailTextZero,
|
|
one: licensesPackageDetailTextOne,
|
|
two: licensesPackageDetailTextTwo,
|
|
many: licensesPackageDetailTextMany,
|
|
few: licensesPackageDetailTextFew,
|
|
other: licensesPackageDetailTextOther,
|
|
locale: _localeName,
|
|
).replaceFirst(r'$licenseCount', formatDecimal(licenseCount));
|
|
}
|
|
|
|
/// The "zero" form of [remainingTextFieldCharacterCount].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [remainingTextFieldCharacterCountZero], the "zero" form
|
|
/// * [remainingTextFieldCharacterCountOne], the "one" form
|
|
/// * [remainingTextFieldCharacterCountTwo], the "two" form
|
|
/// * [remainingTextFieldCharacterCountFew], the "few" form
|
|
/// * [remainingTextFieldCharacterCountMany], the "many" form
|
|
/// * [remainingTextFieldCharacterCountOther], the "other" form
|
|
@protected
|
|
String? get remainingTextFieldCharacterCountZero => null;
|
|
|
|
/// The "one" form of [remainingTextFieldCharacterCount].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [remainingTextFieldCharacterCountZero], the "zero" form
|
|
/// * [remainingTextFieldCharacterCountOne], the "one" form
|
|
/// * [remainingTextFieldCharacterCountTwo], the "two" form
|
|
/// * [remainingTextFieldCharacterCountFew], the "few" form
|
|
/// * [remainingTextFieldCharacterCountMany], the "many" form
|
|
/// * [remainingTextFieldCharacterCountOther], the "other" form
|
|
@protected
|
|
String? get remainingTextFieldCharacterCountOne => null;
|
|
|
|
/// The "two" form of [remainingTextFieldCharacterCount].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [remainingTextFieldCharacterCountZero], the "zero" form
|
|
/// * [remainingTextFieldCharacterCountOne], the "one" form
|
|
/// * [remainingTextFieldCharacterCountTwo], the "two" form
|
|
/// * [remainingTextFieldCharacterCountFew], the "few" form
|
|
/// * [remainingTextFieldCharacterCountMany], the "many" form
|
|
/// * [remainingTextFieldCharacterCountOther], the "other" form
|
|
@protected
|
|
String? get remainingTextFieldCharacterCountTwo => null;
|
|
|
|
/// The "many" form of [remainingTextFieldCharacterCount].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [remainingTextFieldCharacterCountZero], the "zero" form
|
|
/// * [remainingTextFieldCharacterCountOne], the "one" form
|
|
/// * [remainingTextFieldCharacterCountTwo], the "two" form
|
|
/// * [remainingTextFieldCharacterCountFew], the "few" form
|
|
/// * [remainingTextFieldCharacterCountMany], the "many" form
|
|
/// * [remainingTextFieldCharacterCountOther], the "other" form
|
|
@protected
|
|
String? get remainingTextFieldCharacterCountMany => null;
|
|
|
|
/// The "few" form of [remainingTextFieldCharacterCount].
|
|
///
|
|
/// This form is optional.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [remainingTextFieldCharacterCountZero], the "zero" form
|
|
/// * [remainingTextFieldCharacterCountOne], the "one" form
|
|
/// * [remainingTextFieldCharacterCountTwo], the "two" form
|
|
/// * [remainingTextFieldCharacterCountFew], the "few" form
|
|
/// * [remainingTextFieldCharacterCountMany], the "many" form
|
|
/// * [remainingTextFieldCharacterCountOther], the "other" form
|
|
@protected
|
|
String? get remainingTextFieldCharacterCountFew => null;
|
|
|
|
/// The "other" form of [remainingTextFieldCharacterCount].
|
|
///
|
|
/// This form is required.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Intl.plural], to which this form is passed.
|
|
/// * [remainingTextFieldCharacterCountZero], the "zero" form
|
|
/// * [remainingTextFieldCharacterCountOne], the "one" form
|
|
/// * [remainingTextFieldCharacterCountTwo], the "two" form
|
|
/// * [remainingTextFieldCharacterCountFew], the "few" form
|
|
/// * [remainingTextFieldCharacterCountMany], the "many" form
|
|
/// * [remainingTextFieldCharacterCountOther], the "other" form
|
|
@protected
|
|
String get remainingTextFieldCharacterCountOther;
|
|
|
|
@override
|
|
String remainingTextFieldCharacterCount(int remaining) {
|
|
return intl.Intl.pluralLogic(
|
|
remaining,
|
|
zero: remainingTextFieldCharacterCountZero,
|
|
one: remainingTextFieldCharacterCountOne,
|
|
two: remainingTextFieldCharacterCountTwo,
|
|
many: remainingTextFieldCharacterCountMany,
|
|
few: remainingTextFieldCharacterCountFew,
|
|
other: remainingTextFieldCharacterCountOther,
|
|
locale: _localeName,
|
|
).replaceFirst(r'$remainingCount', formatDecimal(remaining));
|
|
}
|
|
|
|
@override
|
|
ScriptCategory get scriptCategory;
|
|
|
|
/// A [LocalizationsDelegate] for [MaterialLocalizations].
|
|
///
|
|
/// Most internationalized apps will use [GlobalMaterialLocalizations.delegates]
|
|
/// as the value of [MaterialApp.localizationsDelegates] to include
|
|
/// the localizations for both the material and widget libraries.
|
|
static const LocalizationsDelegate<MaterialLocalizations> delegate = _MaterialLocalizationsDelegate();
|
|
|
|
/// A value for [MaterialApp.localizationsDelegates] that's typically used by
|
|
/// internationalized apps.
|
|
///
|
|
/// ## Sample code
|
|
///
|
|
/// To include the localizations provided by this class and by
|
|
/// [GlobalWidgetsLocalizations] in a [MaterialApp],
|
|
/// use [GlobalMaterialLocalizations.delegates] as the value of
|
|
/// [MaterialApp.localizationsDelegates], and specify the locales your
|
|
/// app supports with [MaterialApp.supportedLocales]:
|
|
///
|
|
/// ```dart
|
|
/// MaterialApp(
|
|
/// localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
|
/// supportedLocales: [
|
|
/// const Locale('en', 'US'), // English
|
|
/// const Locale('he', 'IL'), // Hebrew
|
|
/// ],
|
|
/// // ...
|
|
/// )
|
|
/// ```
|
|
static const List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[
|
|
GlobalCupertinoLocalizations.delegate,
|
|
GlobalMaterialLocalizations.delegate,
|
|
GlobalWidgetsLocalizations.delegate,
|
|
];
|
|
}
|
|
|
|
/// Finds the [TimeOfDayFormat] to use instead of the `original` when the
|
|
/// `original` uses 12-hour format and [MediaQueryData.alwaysUse24HourFormat]
|
|
/// is true.
|
|
TimeOfDayFormat _get24HourVersionOf(TimeOfDayFormat original) {
|
|
switch (original) {
|
|
case TimeOfDayFormat.HH_colon_mm:
|
|
case TimeOfDayFormat.HH_dot_mm:
|
|
case TimeOfDayFormat.frenchCanadian:
|
|
case TimeOfDayFormat.H_colon_mm:
|
|
return original;
|
|
case TimeOfDayFormat.h_colon_mm_space_a:
|
|
case TimeOfDayFormat.a_space_h_colon_mm:
|
|
return TimeOfDayFormat.HH_colon_mm;
|
|
}
|
|
}
|
|
|
|
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
|
|
const _MaterialLocalizationsDelegate();
|
|
|
|
@override
|
|
bool isSupported(Locale locale) => kMaterialSupportedLanguages.contains(locale.languageCode);
|
|
|
|
static final Map<Locale, Future<MaterialLocalizations>> _loadedTranslations = <Locale, Future<MaterialLocalizations>>{};
|
|
|
|
@override
|
|
Future<MaterialLocalizations> load(Locale locale) {
|
|
assert(isSupported(locale));
|
|
return _loadedTranslations.putIfAbsent(locale, () {
|
|
util.loadDateIntlDataIfNotLoaded();
|
|
|
|
final String localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
|
assert(
|
|
locale.toString() == localeName,
|
|
'Flutter does not support the non-standard locale form $locale (which '
|
|
'might be $localeName',
|
|
);
|
|
|
|
intl.DateFormat fullYearFormat;
|
|
intl.DateFormat compactDateFormat;
|
|
intl.DateFormat shortDateFormat;
|
|
intl.DateFormat mediumDateFormat;
|
|
intl.DateFormat longDateFormat;
|
|
intl.DateFormat yearMonthFormat;
|
|
intl.DateFormat shortMonthDayFormat;
|
|
if (intl.DateFormat.localeExists(localeName)) {
|
|
fullYearFormat = intl.DateFormat.y(localeName);
|
|
compactDateFormat = intl.DateFormat.yMd(localeName);
|
|
shortDateFormat = intl.DateFormat.yMMMd(localeName);
|
|
mediumDateFormat = intl.DateFormat.MMMEd(localeName);
|
|
longDateFormat = intl.DateFormat.yMMMMEEEEd(localeName);
|
|
yearMonthFormat = intl.DateFormat.yMMMM(localeName);
|
|
shortMonthDayFormat = intl.DateFormat.MMMd(localeName);
|
|
} else if (intl.DateFormat.localeExists(locale.languageCode)) {
|
|
fullYearFormat = intl.DateFormat.y(locale.languageCode);
|
|
compactDateFormat = intl.DateFormat.yMd(locale.languageCode);
|
|
shortDateFormat = intl.DateFormat.yMMMd(locale.languageCode);
|
|
mediumDateFormat = intl.DateFormat.MMMEd(locale.languageCode);
|
|
longDateFormat = intl.DateFormat.yMMMMEEEEd(locale.languageCode);
|
|
yearMonthFormat = intl.DateFormat.yMMMM(locale.languageCode);
|
|
shortMonthDayFormat = intl.DateFormat.MMMd(locale.languageCode);
|
|
} else {
|
|
fullYearFormat = intl.DateFormat.y();
|
|
compactDateFormat = intl.DateFormat.yMd();
|
|
shortDateFormat = intl.DateFormat.yMMMd();
|
|
mediumDateFormat = intl.DateFormat.MMMEd();
|
|
longDateFormat = intl.DateFormat.yMMMMEEEEd();
|
|
yearMonthFormat = intl.DateFormat.yMMMM();
|
|
shortMonthDayFormat = intl.DateFormat.MMMd();
|
|
}
|
|
|
|
intl.NumberFormat decimalFormat;
|
|
intl.NumberFormat twoDigitZeroPaddedFormat;
|
|
if (intl.NumberFormat.localeExists(localeName)) {
|
|
decimalFormat = intl.NumberFormat.decimalPattern(localeName);
|
|
twoDigitZeroPaddedFormat = intl.NumberFormat('00', localeName);
|
|
} else if (intl.NumberFormat.localeExists(locale.languageCode)) {
|
|
decimalFormat = intl.NumberFormat.decimalPattern(locale.languageCode);
|
|
twoDigitZeroPaddedFormat = intl.NumberFormat('00', locale.languageCode);
|
|
} else {
|
|
decimalFormat = intl.NumberFormat.decimalPattern();
|
|
twoDigitZeroPaddedFormat = intl.NumberFormat('00');
|
|
}
|
|
|
|
return SynchronousFuture<MaterialLocalizations>(getMaterialTranslation(
|
|
locale,
|
|
fullYearFormat,
|
|
compactDateFormat,
|
|
shortDateFormat,
|
|
mediumDateFormat,
|
|
longDateFormat,
|
|
yearMonthFormat,
|
|
shortMonthDayFormat,
|
|
decimalFormat,
|
|
twoDigitZeroPaddedFormat,
|
|
)!);
|
|
});
|
|
}
|
|
|
|
@override
|
|
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
|
|
|
|
@override
|
|
String toString() => 'GlobalMaterialLocalizations.delegate(${kMaterialSupportedLanguages.length} locales)';
|
|
}
|