flutter_localization optional package (#12410)
This commit is contained in:
parent
f07170b45b
commit
c3d56b1dad
@ -7,7 +7,7 @@ dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -19,7 +19,7 @@ dev_dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -11,7 +11,7 @@ dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -6,7 +6,7 @@ dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -57,13 +57,13 @@ Future<Null> _verifyInternationalizations() async {
|
||||
dart,
|
||||
<String>[
|
||||
path.join('dev', 'tools', 'gen_localizations.dart'),
|
||||
path.join('packages', 'flutter', 'lib', 'src', 'material', 'i18n'),
|
||||
path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n'),
|
||||
'material'
|
||||
],
|
||||
workingDirectory: flutterRoot,
|
||||
);
|
||||
|
||||
final String localizationsFile = path.join('packages', 'flutter', 'lib', 'src', 'material', 'i18n', 'localizations.dart');
|
||||
final String localizationsFile = path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n', 'localizations.dart');
|
||||
|
||||
final EvalResult sourceContents = await _evalCommand(
|
||||
'cat',
|
||||
@ -156,6 +156,7 @@ Future<Null> _runTests() async {
|
||||
|
||||
// Run tests.
|
||||
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter'));
|
||||
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'));
|
||||
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'));
|
||||
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'));
|
||||
await _pubRunTest(path.join(flutterRoot, 'packages', 'flutter_tools'));
|
||||
|
@ -24,7 +24,7 @@ dev_dependencies:
|
||||
|
||||
archive: 1.0.31 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
|
||||
browser: 0.10.0+2 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
|
@ -9,7 +9,7 @@ dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -9,7 +9,7 @@ dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -9,7 +9,7 @@ dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -14,7 +14,7 @@ dev_dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
|
@ -10,7 +10,7 @@ dev_dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -10,7 +10,7 @@ dependencies:
|
||||
path: 1.4.2
|
||||
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -13,7 +13,7 @@ dev_dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -21,7 +21,7 @@ dev_dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
convert: 2.0.1 # TRANSITIVE DEPENDENCY
|
||||
|
@ -7,7 +7,7 @@ dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -10,7 +10,7 @@ dev_dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -9,7 +9,7 @@ dev_dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -12,7 +12,7 @@ dev_dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -12,7 +12,7 @@ dev_dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -6,7 +6,7 @@ dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -13,6 +13,7 @@ import 'package:flutter/rendering.dart' show
|
||||
debugPaintLayerBordersEnabled,
|
||||
debugPaintPointersEnabled,
|
||||
debugRepaintRainbowEnabled;
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
import 'stock_data.dart';
|
||||
import 'stock_home.dart';
|
||||
@ -118,8 +119,10 @@ class StocksAppState extends State<StocksApp> {
|
||||
return new MaterialApp(
|
||||
title: 'Stocks',
|
||||
theme: theme,
|
||||
localizationsDelegates: <_StocksLocalizationsDelegate>[
|
||||
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
|
||||
new _StocksLocalizationsDelegate(),
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
|
@ -2,6 +2,8 @@ name: stocks
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
intl: 0.15.1
|
||||
intl_translation: 0.15.0
|
||||
http: 0.11.3+14
|
||||
@ -15,7 +17,7 @@ dev_dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
|
@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -26,17 +24,6 @@ const TextStyle _errorTextStyle = const TextStyle(
|
||||
decorationStyle: TextDecorationStyle.double
|
||||
);
|
||||
|
||||
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
|
||||
const _MaterialLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<MaterialLocalizations> load(Locale locale) => DefaultMaterialLocalizations.load(locale);
|
||||
|
||||
@override
|
||||
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
|
||||
/// An application that uses material design.
|
||||
///
|
||||
/// A convenience widget that wraps a number of widgets that are commonly
|
||||
@ -463,7 +450,7 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
|
||||
if (widget.localizationsDelegates != null)
|
||||
yield* widget.localizationsDelegates;
|
||||
yield const _MaterialLocalizationsDelegate();
|
||||
yield DefaultMaterialLocalizations.delegate;
|
||||
}
|
||||
|
||||
RectTween _createRectTween(Rect begin, Rect end) {
|
||||
|
@ -295,8 +295,8 @@ class DayPicker extends StatelessWidget {
|
||||
List<Widget> _getDayHeaders(TextStyle headerStyle, MaterialLocalizations localizations) {
|
||||
final List<Widget> result = <Widget>[];
|
||||
for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) {
|
||||
final String weekDay = localizations.narrowWeekDays[i];
|
||||
result.add(new Center(child: new Text(weekDay, style: headerStyle)));
|
||||
final String weekday = localizations.narrowWeekdays[i];
|
||||
result.add(new Center(child: new Text(weekday, style: headerStyle)));
|
||||
if (i == (localizations.firstDayOfWeekIndex - 1) % 7)
|
||||
break;
|
||||
}
|
||||
@ -350,19 +350,19 @@ class DayPicker extends StatelessWidget {
|
||||
/// - [DateTime.weekday] provides a 1-based index into days of week, with 1
|
||||
/// falling on Monday.
|
||||
/// - [MaterialLocalizations.firstDayOfWeekIndex] provides a 0-based index
|
||||
/// into the [MaterialLocalizations.narrowWeekDays] list.
|
||||
/// - [MaterialLocalizations.narrowWeekDays] list provides localized names of
|
||||
/// into the [MaterialLocalizations.narrowWeekdays] list.
|
||||
/// - [MaterialLocalizations.narrowWeekdays] list provides localized names of
|
||||
/// days of week, always starting with Sunday and ending with Saturday.
|
||||
int _computeFirstDayOffset(int year, int month, MaterialLocalizations localizations) {
|
||||
// 0-based day of week, with 0 representing Monday.
|
||||
final int weekDayFromMonday = new DateTime(year, month).weekday - 1;
|
||||
final int weekdayFromMonday = new DateTime(year, month).weekday - 1;
|
||||
// 0-based day of week, with 0 representing Sunday.
|
||||
final int firstDayOfWeekFromSunday = localizations.firstDayOfWeekIndex;
|
||||
// firstDayOfWeekFromSunday recomputed to be Monday-based
|
||||
final int firstDayOfWeekFromMonday = (firstDayOfWeekFromSunday - 1) % 7;
|
||||
// Number of days between the first day of week appearing on the calendar,
|
||||
// and the day corresponding to the 1-st of the month.
|
||||
return (weekDayFromMonday - firstDayOfWeekFromMonday) % 7;
|
||||
return (weekdayFromMonday - firstDayOfWeekFromMonday) % 7;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -6,11 +6,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'package:intl/date_symbols.dart' as intl;
|
||||
import 'package:intl/date_symbol_data_local.dart' as intl_local_date_data;
|
||||
|
||||
import 'i18n/localizations.dart';
|
||||
import 'time.dart';
|
||||
import 'typography.dart';
|
||||
|
||||
@ -18,8 +14,10 @@ import 'typography.dart';
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [DefaultMaterialLocalizations], which implements this interface
|
||||
/// and supports a variety of locales.
|
||||
/// * [DefaultMaterialLocalizations], the default, English-only, implementation
|
||||
/// of this interface.
|
||||
/// * [GlobalMaterialLocalizations], which provides material localizations for
|
||||
/// many languages.
|
||||
abstract class MaterialLocalizations {
|
||||
/// The tooltip for the leading [AppBar] menu (aka 'hamburger') button.
|
||||
String get openAppDrawerTooltip;
|
||||
@ -155,17 +153,17 @@ abstract class MaterialLocalizations {
|
||||
/// - US English: S, M, T, W, T, F, S
|
||||
/// - Russian: вс, пн, вт, ср, чт, пт, сб - notice that the list begins with
|
||||
/// вс (Sunday) even though the first day of week for Russian is Monday.
|
||||
List<String> get narrowWeekDays;
|
||||
List<String> get narrowWeekdays;
|
||||
|
||||
/// Index of the first day of week, where 0 points to Sunday, and 6 points to
|
||||
/// Saturday.
|
||||
///
|
||||
/// This getter is compatible with [narrowWeekDays]. For example:
|
||||
/// This getter is compatible with [narrowWeekdays]. For example:
|
||||
///
|
||||
/// ```dart
|
||||
/// var localizations = MaterialLocalizations.of(context);
|
||||
/// // The name of the first day of week for the current locale.
|
||||
/// var firstDayOfWeek = localizations.narrowWeekDays[localizations.firstDayOfWeekIndex];
|
||||
/// var firstDayOfWeek = localizations.narrowWeekdays[localizations.firstDayOfWeekIndex];
|
||||
/// ```
|
||||
int get firstDayOfWeekIndex;
|
||||
|
||||
@ -186,173 +184,118 @@ abstract class MaterialLocalizations {
|
||||
}
|
||||
}
|
||||
|
||||
/// Localized strings for the material widgets.
|
||||
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
|
||||
const _MaterialLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<MaterialLocalizations> load(Locale locale) => DefaultMaterialLocalizations.load(locale);
|
||||
|
||||
@override
|
||||
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
/// US English strings for the material widgets.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [GlobalMaterialLocalizations], which provides material localizations for
|
||||
/// many languages.
|
||||
/// * [MaterialApp.delegates], which automatically includes
|
||||
/// [DefaultMaterialLocalizations.delegate] by default.
|
||||
class DefaultMaterialLocalizations implements MaterialLocalizations {
|
||||
/// Constructs an object that defines the material widgets' localized strings
|
||||
/// for the given `locale`.
|
||||
/// for US English (only).
|
||||
///
|
||||
/// [LocalizationsDelegate] implementations typically call the static [load]
|
||||
/// function, rather than constructing this class directly.
|
||||
DefaultMaterialLocalizations(this.locale)
|
||||
: assert(locale != null),
|
||||
this._localeName = _computeLocaleName(locale) {
|
||||
_loadDateIntlDataIfNotLoaded();
|
||||
const DefaultMaterialLocalizations();
|
||||
|
||||
if (localizations.containsKey(locale.languageCode))
|
||||
_nameToValue.addAll(localizations[locale.languageCode]);
|
||||
if (localizations.containsKey(_localeName))
|
||||
_nameToValue.addAll(localizations[_localeName]);
|
||||
|
||||
const String kMediumDatePattern = 'E, MMM\u00a0d';
|
||||
if (intl.DateFormat.localeExists(_localeName)) {
|
||||
_fullYearFormat = new intl.DateFormat.y(_localeName);
|
||||
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern, _localeName);
|
||||
_yearMonthFormat = new intl.DateFormat('yMMMM', _localeName);
|
||||
} else if (intl.DateFormat.localeExists(locale.languageCode)) {
|
||||
_fullYearFormat = new intl.DateFormat.y(locale.languageCode);
|
||||
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern, locale.languageCode);
|
||||
_yearMonthFormat = new intl.DateFormat('yMMMM', locale.languageCode);
|
||||
} else {
|
||||
_fullYearFormat = new intl.DateFormat.y();
|
||||
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern);
|
||||
_yearMonthFormat = new intl.DateFormat('yMMMM');
|
||||
}
|
||||
// Ordered to match DateTime.MONDAY=1, DateTime.SUNDAY=6
|
||||
static const List<String>_shortWeekdays = const <String>[
|
||||
'Mon',
|
||||
'Tue',
|
||||
'Wed',
|
||||
'Thu',
|
||||
'Fri',
|
||||
'Sat',
|
||||
'Sun',
|
||||
];
|
||||
|
||||
if (intl.NumberFormat.localeExists(_localeName)) {
|
||||
_decimalFormat = new intl.NumberFormat.decimalPattern(_localeName);
|
||||
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00', _localeName);
|
||||
} else if (intl.NumberFormat.localeExists(locale.languageCode)) {
|
||||
_decimalFormat = new intl.NumberFormat.decimalPattern(locale.languageCode);
|
||||
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00', locale.languageCode);
|
||||
} else {
|
||||
_decimalFormat = new intl.NumberFormat.decimalPattern();
|
||||
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00');
|
||||
}
|
||||
}
|
||||
static const List<String> _narrowWeekdays = const <String>[
|
||||
'S',
|
||||
'M',
|
||||
'T',
|
||||
'W',
|
||||
'T',
|
||||
'F',
|
||||
'S',
|
||||
];
|
||||
|
||||
/// The locale for which the values of this class's localized resources
|
||||
/// have been translated.
|
||||
final Locale locale;
|
||||
static const List<String> _shortMonths = const <String>[
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec',
|
||||
];
|
||||
|
||||
final String _localeName;
|
||||
|
||||
final Map<String, String> _nameToValue = <String, String>{};
|
||||
|
||||
/// Formats numbers using variable length format with no zero padding.
|
||||
///
|
||||
/// See also [_twoDigitZeroPaddedFormat].
|
||||
intl.NumberFormat _decimalFormat;
|
||||
|
||||
/// Formats numbers as two-digits.
|
||||
///
|
||||
/// If the number is less than 10, zero-pads it.
|
||||
intl.NumberFormat _twoDigitZeroPaddedFormat;
|
||||
|
||||
/// Full unabbreviated year format, e.g. 2017 rather than 17.
|
||||
intl.DateFormat _fullYearFormat;
|
||||
|
||||
intl.DateFormat _mediumDateFormat;
|
||||
|
||||
intl.DateFormat _yearMonthFormat;
|
||||
|
||||
static String _computeLocaleName(Locale locale) {
|
||||
final String localeName = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
|
||||
return intl.Intl.canonicalizedLocale(localeName);
|
||||
}
|
||||
|
||||
// TODO(hmuller): the rules for mapping from an integer value to
|
||||
// "one" or "two" etc. are locale specific and an additional "few" category
|
||||
// is needed. See http://cldr.unicode.org/index/cldr-spec/plural-rules
|
||||
String _nameToPluralValue(int count, String key) {
|
||||
String text;
|
||||
if (count == 0)
|
||||
text = _nameToValue['${key}Zero'];
|
||||
else if (count == 1)
|
||||
text = _nameToValue['${key}One'];
|
||||
else if (count == 2)
|
||||
text = _nameToValue['${key}Two'];
|
||||
else if (count > 2)
|
||||
text = _nameToValue['${key}Many'];
|
||||
text ??= _nameToValue['${key}Other'];
|
||||
assert(text != null);
|
||||
return text;
|
||||
}
|
||||
static const List<String> _months = const <String>[
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
|
||||
@override
|
||||
String formatHour(TimeOfDay timeOfDay) {
|
||||
switch (hourFormat(of: timeOfDayFormat)) {
|
||||
case HourFormat.HH:
|
||||
return _twoDigitZeroPaddedFormat.format(timeOfDay.hour);
|
||||
case HourFormat.H:
|
||||
assert(hourFormat(of: timeOfDayFormat) == HourFormat.h);
|
||||
return formatDecimal(timeOfDay.hour);
|
||||
case HourFormat.h:
|
||||
final int hour = timeOfDay.hourOfPeriod;
|
||||
return formatDecimal(hour == 0 ? 12 : hour);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String formatMinute(TimeOfDay timeOfDay) {
|
||||
return _twoDigitZeroPaddedFormat.format(timeOfDay.minute);
|
||||
final int minute = timeOfDay.minute;
|
||||
return minute < 10 ? '0$minute' : minute.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
String formatYear(DateTime date) {
|
||||
return _fullYearFormat.format(date);
|
||||
}
|
||||
String formatYear(DateTime date) => date.year.toString();
|
||||
|
||||
@override
|
||||
String formatMediumDate(DateTime date) {
|
||||
return _mediumDateFormat.format(date);
|
||||
final String day = _shortWeekdays[date.weekday - DateTime.MONDAY];
|
||||
final String month = _shortMonths[date.month - DateTime.JANUARY];
|
||||
return '$day, $month ${date.day}';
|
||||
}
|
||||
|
||||
@override
|
||||
String formatMonthYear(DateTime date) {
|
||||
return _yearMonthFormat.format(date);
|
||||
final String year = formatYear(date);
|
||||
final String month = _months[date.month - DateTime.JANUARY];
|
||||
return '$month $year';
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get narrowWeekDays {
|
||||
return _fullYearFormat.dateSymbols.NARROWWEEKDAYS;
|
||||
}
|
||||
List<String> get narrowWeekdays => _narrowWeekdays;
|
||||
|
||||
@override
|
||||
int get firstDayOfWeekIndex => (_fullYearFormat.dateSymbols.FIRSTDAYOFWEEK + 1) % 7;
|
||||
|
||||
/// Formats a [number] using local decimal number format.
|
||||
///
|
||||
/// Inserts locale-appropriate thousands separator, if necessary.
|
||||
String formatDecimal(int number) {
|
||||
return _decimalFormat.format(number);
|
||||
}
|
||||
|
||||
@override
|
||||
String formatTimeOfDay(TimeOfDay timeOfDay) {
|
||||
// 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.
|
||||
switch (timeOfDayFormat) {
|
||||
case TimeOfDayFormat.h_colon_mm_space_a:
|
||||
return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)} ${_formatDayPeriod(timeOfDay)}';
|
||||
case TimeOfDayFormat.H_colon_mm:
|
||||
case TimeOfDayFormat.HH_colon_mm:
|
||||
return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)}';
|
||||
case TimeOfDayFormat.HH_dot_mm:
|
||||
return '${formatHour(timeOfDay)}.${formatMinute(timeOfDay)}';
|
||||
case TimeOfDayFormat.a_space_h_colon_mm:
|
||||
return '${_formatDayPeriod(timeOfDay)} ${formatHour(timeOfDay)}:${formatMinute(timeOfDay)}';
|
||||
case TimeOfDayFormat.frenchCanadian:
|
||||
return '${formatHour(timeOfDay)} h ${formatMinute(timeOfDay)}';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
int get firstDayOfWeekIndex => 0; // narrowWeekdays[0] is 'S' for Sunday
|
||||
|
||||
String _formatDayPeriod(TimeOfDay timeOfDay) {
|
||||
switch (timeOfDay.period) {
|
||||
@ -364,166 +307,142 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String get openAppDrawerTooltip => _nameToValue['openAppDrawerTooltip'];
|
||||
/// Formats an integer, inserting thousands separators as needed.
|
||||
String formatDecimal(int number) {
|
||||
if (number > -1000 && number < 1000)
|
||||
return number.toString();
|
||||
|
||||
@override
|
||||
String get backButtonTooltip => _nameToValue['backButtonTooltip'];
|
||||
|
||||
@override
|
||||
String get closeButtonTooltip => _nameToValue['closeButtonTooltip'];
|
||||
|
||||
@override
|
||||
String get nextMonthTooltip => _nameToValue['nextMonthTooltip'];
|
||||
|
||||
@override
|
||||
String get previousMonthTooltip => _nameToValue['previousMonthTooltip'];
|
||||
|
||||
@override
|
||||
String get nextPageTooltip => _nameToValue['nextPageTooltip'];
|
||||
|
||||
@override
|
||||
String get previousPageTooltip => _nameToValue['previousPageTooltip'];
|
||||
|
||||
@override
|
||||
String get showMenuTooltip => _nameToValue['showMenuTooltip'];
|
||||
|
||||
@override
|
||||
String aboutListTileTitle(String applicationName) {
|
||||
final String text = _nameToValue['aboutListTileTitle'];
|
||||
return text.replaceFirst(r'$applicationName', applicationName);
|
||||
final String digits = number.abs().toString();
|
||||
final StringBuffer result = new StringBuffer(number < 0 ? '-' : '');
|
||||
final int maxDigitIndex = digits.length - 1;
|
||||
for (int i = 0; i <= maxDigitIndex; i += 1) {
|
||||
result.write(digits[i]);
|
||||
if (i < maxDigitIndex && (maxDigitIndex - i) % 3 == 0)
|
||||
result.write(',');
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
String get licensesPageTitle => _nameToValue['licensesPageTitle'];
|
||||
String formatTimeOfDay(TimeOfDay timeOfDay) {
|
||||
assert(timeOfDayFormat == TimeOfDayFormat.h_colon_mm_space_a);
|
||||
// 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.
|
||||
return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)} ${_formatDayPeriod(timeOfDay)}';
|
||||
}
|
||||
|
||||
@override
|
||||
String get openAppDrawerTooltip => 'Open navigation menu';
|
||||
|
||||
@override
|
||||
String get backButtonTooltip => 'Back';
|
||||
|
||||
@override
|
||||
String get closeButtonTooltip => 'Close';
|
||||
|
||||
@override
|
||||
String get nextMonthTooltip => 'Next month';
|
||||
|
||||
@override
|
||||
String get previousMonthTooltip => 'Previous month';
|
||||
|
||||
@override
|
||||
String get nextPageTooltip => 'Next page';
|
||||
|
||||
@override
|
||||
String get previousPageTooltip => 'Previous page';
|
||||
|
||||
@override
|
||||
String get showMenuTooltip => 'Show menu';
|
||||
|
||||
@override
|
||||
String aboutListTileTitle(String applicationName) => 'About $applicationName';
|
||||
|
||||
@override
|
||||
String get licensesPageTitle => 'Licenses';
|
||||
|
||||
@override
|
||||
String pageRowsInfoTitle(int firstRow, int lastRow, int rowCount, bool rowCountIsApproximate) {
|
||||
String text = rowCountIsApproximate ? _nameToValue['pageRowsInfoTitleApproximate'] : null;
|
||||
text ??= _nameToValue['pageRowsInfoTitle'];
|
||||
assert(text != null, 'A $locale localization was not found for pageRowsInfoTitle or pageRowsInfoTitleApproximate');
|
||||
// TODO(hansmuller): this could be more efficient.
|
||||
return text
|
||||
.replaceFirst(r'$firstRow', formatDecimal(firstRow))
|
||||
.replaceFirst(r'$lastRow', formatDecimal(lastRow))
|
||||
.replaceFirst(r'$rowCount', formatDecimal(rowCount));
|
||||
return rowCountIsApproximate
|
||||
? '$firstRow–$lastRow of about $rowCount'
|
||||
: '$firstRow–$lastRow of $rowCount';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rowsPerPageTitle => _nameToValue['rowsPerPageTitle'];
|
||||
String get rowsPerPageTitle => 'Rows per page';
|
||||
|
||||
@override
|
||||
String selectedRowCountTitle(int selectedRowCount) {
|
||||
return _nameToPluralValue(selectedRowCount, 'selectedRowCountTitle') // asserts on no match
|
||||
.replaceFirst(r'$selectedRowCount', formatDecimal(selectedRowCount));
|
||||
switch (selectedRowCount) {
|
||||
case 0:
|
||||
return 'No items selected';
|
||||
case 1:
|
||||
return '1 item selected';
|
||||
default:
|
||||
return '$selectedRowCount items selected';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String get cancelButtonLabel => _nameToValue['cancelButtonLabel'];
|
||||
String get cancelButtonLabel => 'CANCEL';
|
||||
|
||||
@override
|
||||
String get closeButtonLabel => _nameToValue['closeButtonLabel'];
|
||||
String get closeButtonLabel => 'CLOSE';
|
||||
|
||||
@override
|
||||
String get continueButtonLabel => _nameToValue['continueButtonLabel'];
|
||||
String get continueButtonLabel => 'CONTINUE';
|
||||
|
||||
@override
|
||||
String get copyButtonLabel => _nameToValue['copyButtonLabel'];
|
||||
String get copyButtonLabel => 'COPY';
|
||||
|
||||
@override
|
||||
String get cutButtonLabel => _nameToValue['cutButtonLabel'];
|
||||
String get cutButtonLabel => 'CUT';
|
||||
|
||||
@override
|
||||
String get okButtonLabel => _nameToValue['okButtonLabel'];
|
||||
String get okButtonLabel => 'OK';
|
||||
|
||||
@override
|
||||
String get pasteButtonLabel => _nameToValue['pasteButtonLabel'];
|
||||
String get pasteButtonLabel => 'PASTE';
|
||||
|
||||
@override
|
||||
String get selectAllButtonLabel => _nameToValue['selectAllButtonLabel'];
|
||||
String get selectAllButtonLabel => 'SELECT ALL';
|
||||
|
||||
@override
|
||||
String get viewLicensesButtonLabel => _nameToValue['viewLicensesButtonLabel'];
|
||||
String get viewLicensesButtonLabel => 'VIEW LICENSES';
|
||||
|
||||
@override
|
||||
String get anteMeridiemAbbreviation => _nameToValue['anteMeridiemAbbreviation'];
|
||||
String get anteMeridiemAbbreviation => 'AM';
|
||||
|
||||
@override
|
||||
String get postMeridiemAbbreviation => _nameToValue['postMeridiemAbbreviation'];
|
||||
String get postMeridiemAbbreviation => 'PM';
|
||||
|
||||
/// 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 shows the
|
||||
/// short time pattern used in locale en_US
|
||||
@override
|
||||
TimeOfDayFormat get timeOfDayFormat {
|
||||
final String icuShortTimePattern = _nameToValue['timeOfDayFormat'];
|
||||
|
||||
assert(() {
|
||||
if (!_icuTimeOfDayToEnum.containsKey(icuShortTimePattern)) {
|
||||
throw new FlutterError(
|
||||
'"$icuShortTimePattern" is not one of the ICU short time patterns '
|
||||
'supported by the material library. Here is the list of supported '
|
||||
'patterns:\n ' +
|
||||
_icuTimeOfDayToEnum.keys.join('\n ')
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
|
||||
return _icuTimeOfDayToEnum[icuShortTimePattern];
|
||||
}
|
||||
TimeOfDayFormat get timeOfDayFormat => TimeOfDayFormat.h_colon_mm_space_a;
|
||||
|
||||
/// Looks up text geometry defined in [MaterialTextGeometry].
|
||||
@override
|
||||
TextTheme get localTextGeometry => MaterialTextGeometry.forScriptCategory(_nameToValue["scriptCategory"]);
|
||||
TextTheme get localTextGeometry => MaterialTextGeometry.englishLike;
|
||||
|
||||
/// Creates an object that provides localized resource values for the
|
||||
/// for the widgets of the material library.
|
||||
/// Creates an object that provides US English resource values for the material
|
||||
/// library widgets.
|
||||
///
|
||||
/// The [locale] parameter is ignored.
|
||||
///
|
||||
/// This method is typically used to create a [LocalizationsDelegate].
|
||||
/// The [MaterialApp] does so by default.
|
||||
static Future<MaterialLocalizations> load(Locale locale) {
|
||||
return new SynchronousFuture<MaterialLocalizations>(new DefaultMaterialLocalizations(locale));
|
||||
}
|
||||
}
|
||||
|
||||
const Map<String, TimeOfDayFormat> _icuTimeOfDayToEnum = const <String, TimeOfDayFormat>{
|
||||
'HH:mm': TimeOfDayFormat.HH_colon_mm,
|
||||
'HH.mm': TimeOfDayFormat.HH_dot_mm,
|
||||
"HH 'h' mm": TimeOfDayFormat.frenchCanadian,
|
||||
'HH:mm น.': TimeOfDayFormat.HH_colon_mm,
|
||||
'H:mm': TimeOfDayFormat.H_colon_mm,
|
||||
'h:mm a': TimeOfDayFormat.h_colon_mm_space_a,
|
||||
'a h:mm': TimeOfDayFormat.a_space_h_colon_mm,
|
||||
'ah:mm': TimeOfDayFormat.a_space_h_colon_mm,
|
||||
};
|
||||
|
||||
/// Tracks if date i18n data has been loaded.
|
||||
bool _dateIntlDataInitialized = false;
|
||||
|
||||
/// Loads i18n data for dates if it hasn't be loaded yet.
|
||||
///
|
||||
/// Only the first invocation of this function has the effect of loading the
|
||||
/// data. Subsequent invocations have no effect.
|
||||
void _loadDateIntlDataIfNotLoaded() {
|
||||
if (!_dateIntlDataInitialized) {
|
||||
// The returned Future is intentionally dropped on the floor. The
|
||||
// function only returns it to be compatible with the async counterparts.
|
||||
// The Future has no value otherwise.
|
||||
intl_local_date_data.initializeDateFormatting();
|
||||
_dateIntlDataInitialized = true;
|
||||
return new SynchronousFuture<MaterialLocalizations>(const DefaultMaterialLocalizations());
|
||||
}
|
||||
|
||||
/// A [LocalizationsDelegate] that uses [DefaultMaterialLocalizations.load]
|
||||
/// to create an instance of this class.
|
||||
///
|
||||
/// [MaterialApp] automatically adds this value to [MaterialApp.localizationsDelegates].
|
||||
static const LocalizationsDelegate<MaterialLocalizations> delegate = const _MaterialLocalizationsDelegate();
|
||||
}
|
||||
|
@ -44,17 +44,6 @@ typedef Locale LocaleResolutionCallback(Locale locale, Iterable<Locale> supporte
|
||||
/// This function must not return null.
|
||||
typedef String GenerateAppTitle(BuildContext context);
|
||||
|
||||
// Delegate that fetches the default (English) strings.
|
||||
class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
|
||||
const _WidgetsLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
|
||||
|
||||
@override
|
||||
bool shouldReload(_WidgetsLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
/// A convenience class that wraps a number of widgets that are commonly
|
||||
/// required for an application.
|
||||
///
|
||||
@ -423,11 +412,11 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
||||
// by the localizationsDelegates parameter, if any. Only the first delegate
|
||||
// of a particular LocalizationsDelegate.type is loaded so the
|
||||
// localizationsDelegate parameter can be used to override
|
||||
// _WidgetsLocalizationsDelegate.
|
||||
// WidgetsLocalizations.delegate.
|
||||
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
|
||||
if (widget.localizationsDelegates != null)
|
||||
yield* widget.localizationsDelegates;
|
||||
yield const _WidgetsLocalizationsDelegate();
|
||||
yield DefaultWidgetsLocalizations.delegate;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -161,44 +161,50 @@ abstract class WidgetsLocalizations {
|
||||
}
|
||||
}
|
||||
|
||||
/// Localized values for widgets.
|
||||
class DefaultWidgetsLocalizations implements WidgetsLocalizations {
|
||||
/// Construct an object that defines the localized values for the widgets
|
||||
/// library for the given `locale`.
|
||||
///
|
||||
/// [LocalizationsDelegate] implementations typically call the static [load]
|
||||
/// function, rather than constructing this class directly.
|
||||
DefaultWidgetsLocalizations(this.locale) {
|
||||
final String language = locale.languageCode.toLowerCase();
|
||||
_textDirection = _rtlLanguages.contains(language) ? TextDirection.rtl : TextDirection.ltr;
|
||||
}
|
||||
|
||||
// See http://en.wikipedia.org/wiki/Right-to-left
|
||||
static const List<String> _rtlLanguages = const <String>[
|
||||
'ar', // Arabic
|
||||
'fa', // Farsi
|
||||
'he', // Hebrew
|
||||
'ps', // Pashto
|
||||
'sd', // Sindhi
|
||||
'ur', // Urdu
|
||||
];
|
||||
|
||||
/// The locale for which the values of this class's localized resources
|
||||
/// have been translated.
|
||||
final Locale locale;
|
||||
class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
|
||||
const _WidgetsLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
|
||||
|
||||
/// Creates an object that provides localized resource values for the
|
||||
/// lowest levels of the Flutter framework.
|
||||
@override
|
||||
bool shouldReload(_WidgetsLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
/// US English localizations for the widgets library.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [GlobalWidgetsLocalizations], which provides widgets localizations for
|
||||
/// many languages.
|
||||
/// * [WidgetsApp.delegates], which automatically includes
|
||||
/// [DefaultWidgetsLocalizations.delegate] by default.
|
||||
class DefaultWidgetsLocalizations implements WidgetsLocalizations {
|
||||
/// Construct an object that defines the localized values for the widgets
|
||||
/// library for US English (only).
|
||||
///
|
||||
/// [LocalizationsDelegate] implementations typically call the static [load]
|
||||
const DefaultWidgetsLocalizations();
|
||||
|
||||
@override
|
||||
TextDirection get textDirection => TextDirection.ltr;
|
||||
|
||||
/// Creates an object that provides US English resource values for the
|
||||
/// lowest levels of the widgets library.
|
||||
///
|
||||
/// The [locale] parameter is ignored.
|
||||
///
|
||||
/// This method is typically used to create a [LocalizationsDelegate].
|
||||
/// The [WidgetsApp] does so by default.
|
||||
static Future<WidgetsLocalizations> load(Locale locale) {
|
||||
return new SynchronousFuture<WidgetsLocalizations>(new DefaultWidgetsLocalizations(locale));
|
||||
return new SynchronousFuture<WidgetsLocalizations>(const DefaultWidgetsLocalizations());
|
||||
}
|
||||
|
||||
/// A [LocalizationsDelegate] that uses [DefaultWidgetsLocalizations.load]
|
||||
/// to create an instance of this class.
|
||||
///
|
||||
/// [WidgetsApp] automatically adds this value to [WidgetApp.localizationsDelegates].
|
||||
static const LocalizationsDelegate<WidgetsLocalizations> delegate = const _WidgetsLocalizationsDelegate();
|
||||
}
|
||||
|
||||
class _LocalizationsScope extends InheritedWidget {
|
||||
|
@ -8,7 +8,6 @@ dependencies:
|
||||
# To update these, use "flutter update-packages --force-upgrade".
|
||||
collection: 1.14.3
|
||||
http: 0.11.3+14
|
||||
intl: 0.15.1 # TODO(ianh): remove this, see https://github.com/flutter/flutter/issues/12050
|
||||
meta: 1.1.1
|
||||
typed_data: 1.1.4
|
||||
vector_math: 2.0.5
|
||||
@ -23,7 +22,7 @@ dev_dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
@ -35,6 +34,7 @@ dev_dependencies:
|
||||
html: 0.13.2 # TRANSITIVE DEPENDENCY
|
||||
http_multi_server: 2.0.4 # TRANSITIVE DEPENDENCY
|
||||
http_parser: 3.1.1 # TRANSITIVE DEPENDENCY
|
||||
intl: 0.15.1 # TRANSITIVE DEPENDENCY
|
||||
intl_translation: 0.15.0 # TRANSITIVE DEPENDENCY
|
||||
isolate: 1.1.0 # TRANSITIVE DEPENDENCY
|
||||
js: 0.6.1 # TRANSITIVE DEPENDENCY
|
||||
|
@ -4,8 +4,6 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'feedback_tester.dart';
|
||||
|
||||
@ -419,250 +417,4 @@ void main() {
|
||||
expect(await date, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group(DayPicker, () {
|
||||
final Map<Locale, Map<String, dynamic>> testLocales = <Locale, Map<String, dynamic>>{
|
||||
// Tests the default.
|
||||
const Locale('en', 'US'): <String, dynamic>{
|
||||
'textDirection': TextDirection.ltr,
|
||||
'expectedDaysOfWeek': <String>['S', 'M', 'T', 'W', 'T', 'F', 'S'],
|
||||
'expectedDaysOfMonth': new List<String>.generate(30, (int i) => '${i + 1}'),
|
||||
'expectedMonthYearHeader': 'September 2017',
|
||||
},
|
||||
// Tests a different first day of week.
|
||||
const Locale('ru', 'RU'): <String, dynamic>{
|
||||
'textDirection': TextDirection.ltr,
|
||||
'expectedDaysOfWeek': <String>['пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс'],
|
||||
'expectedDaysOfMonth': new List<String>.generate(30, (int i) => '${i + 1}'),
|
||||
'expectedMonthYearHeader': 'сентябрь 2017 г.',
|
||||
},
|
||||
// Tests RTL.
|
||||
// TODO: change to Arabic numerals when these are fixed:
|
||||
// TODO: https://github.com/dart-lang/intl/issues/143
|
||||
// TODO: https://github.com/flutter/flutter/issues/12289
|
||||
const Locale('ar', 'AR'): <String, dynamic>{
|
||||
'textDirection': TextDirection.rtl,
|
||||
'expectedDaysOfWeek': <String>['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'],
|
||||
'expectedDaysOfMonth': new List<String>.generate(30, (int i) => '${i + 1}'),
|
||||
'expectedMonthYearHeader': 'سبتمبر 2017',
|
||||
},
|
||||
};
|
||||
|
||||
for (Locale locale in testLocales.keys) {
|
||||
testWidgets('shows dates for $locale', (WidgetTester tester) async {
|
||||
final List<String> expectedDaysOfWeek = testLocales[locale]['expectedDaysOfWeek'];
|
||||
final List<String> expectedDaysOfMonth = testLocales[locale]['expectedDaysOfMonth'];
|
||||
final String expectedMonthYearHeader = testLocales[locale]['expectedMonthYearHeader'];
|
||||
final TextDirection textDirection = testLocales[locale]['textDirection'];
|
||||
final DateTime baseDate = new DateTime(2017, 9, 27);
|
||||
|
||||
await _pumpBoilerplate(tester, new DayPicker(
|
||||
selectedDate: baseDate,
|
||||
currentDate: baseDate,
|
||||
onChanged: (DateTime newValue) {},
|
||||
firstDate: baseDate.subtract(const Duration(days: 90)),
|
||||
lastDate: baseDate.add(const Duration(days: 90)),
|
||||
displayedMonth: baseDate,
|
||||
), locale: locale, textDirection: textDirection);
|
||||
|
||||
expect(find.text(expectedMonthYearHeader), findsOneWidget);
|
||||
|
||||
expectedDaysOfWeek.forEach((String dayOfWeek) {
|
||||
expect(find.text(dayOfWeek), findsWidgets);
|
||||
});
|
||||
|
||||
Offset previousCellOffset;
|
||||
expectedDaysOfMonth.forEach((String dayOfMonth) {
|
||||
final Finder dayCell = find.descendant(of: find.byType(GridView), matching: find.text(dayOfMonth));
|
||||
expect(dayCell, findsOneWidget);
|
||||
|
||||
// Check that cells are correctly positioned relative to each other,
|
||||
// taking text direction into account.
|
||||
final Offset offset = tester.getCenter(dayCell);
|
||||
if (previousCellOffset != null) {
|
||||
if (textDirection == TextDirection.ltr) {
|
||||
expect(offset.dx > previousCellOffset.dx && offset.dy == previousCellOffset.dy || offset.dy > previousCellOffset.dy, true);
|
||||
} else {
|
||||
expect(offset.dx < previousCellOffset.dx && offset.dy == previousCellOffset.dy || offset.dy > previousCellOffset.dy, true);
|
||||
}
|
||||
}
|
||||
previousCellOffset = offset;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('locale parameter overrides ambient locale', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
locale: const Locale('en', 'US'),
|
||||
supportedLocales: const <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
const Locale('fr', 'CA'),
|
||||
],
|
||||
home: new Material(
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return new FlatButton(
|
||||
onPressed: () async {
|
||||
await showDatePicker(
|
||||
context: context,
|
||||
initialDate: initialDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
locale: const Locale('fr', 'CA'),
|
||||
);
|
||||
},
|
||||
child: const Text('X'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.text('X'));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
final Element dayPicker = tester.element(find.byType(DayPicker));
|
||||
expect(
|
||||
Localizations.localeOf(dayPicker),
|
||||
const Locale('fr', 'CA'),
|
||||
);
|
||||
|
||||
expect(
|
||||
Directionality.of(dayPicker),
|
||||
TextDirection.ltr,
|
||||
);
|
||||
|
||||
await tester.tap(find.text('ANNULER'));
|
||||
});
|
||||
|
||||
testWidgets('textDirection parameter overrides ambient textDirection', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
locale: const Locale('en', 'US'),
|
||||
supportedLocales: const <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
],
|
||||
home: new Material(
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return new FlatButton(
|
||||
onPressed: () async {
|
||||
await showDatePicker(
|
||||
context: context,
|
||||
initialDate: initialDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
textDirection: TextDirection.rtl,
|
||||
);
|
||||
},
|
||||
child: const Text('X'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.text('X'));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
final Element dayPicker = tester.element(find.byType(DayPicker));
|
||||
expect(
|
||||
Directionality.of(dayPicker),
|
||||
TextDirection.rtl,
|
||||
);
|
||||
|
||||
await tester.tap(find.text('CANCEL'));
|
||||
});
|
||||
|
||||
testWidgets('textDirection parameter takes precendence over locale parameter', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
locale: const Locale('en', 'US'),
|
||||
supportedLocales: const <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
const Locale('fr', 'CA'),
|
||||
],
|
||||
home: new Material(
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return new FlatButton(
|
||||
onPressed: () async {
|
||||
await showDatePicker(
|
||||
context: context,
|
||||
initialDate: initialDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
locale: const Locale('fr', 'CA'),
|
||||
textDirection: TextDirection.rtl,
|
||||
);
|
||||
},
|
||||
child: const Text('X'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.text('X'));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
final Element dayPicker = tester.element(find.byType(DayPicker));
|
||||
expect(
|
||||
Localizations.localeOf(dayPicker),
|
||||
const Locale('fr', 'CA'),
|
||||
);
|
||||
|
||||
expect(
|
||||
Directionality.of(dayPicker),
|
||||
TextDirection.rtl,
|
||||
);
|
||||
|
||||
await tester.tap(find.text('ANNULER'));
|
||||
});
|
||||
}
|
||||
|
||||
Future<Null> _pumpBoilerplate(
|
||||
WidgetTester tester,
|
||||
Widget child, {
|
||||
Locale locale = const Locale('en', 'US'),
|
||||
TextDirection textDirection: TextDirection.ltr
|
||||
}) async {
|
||||
await tester.pumpWidget(new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Localizations(
|
||||
locale: locale,
|
||||
delegates: <LocalizationsDelegate<dynamic>>[
|
||||
new _MaterialLocalizationsDelegate(
|
||||
new DefaultMaterialLocalizations(locale),
|
||||
),
|
||||
const DefaultWidgetsLocalizationsDelegate(),
|
||||
],
|
||||
child: child,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
|
||||
const _MaterialLocalizationsDelegate(this.localizations);
|
||||
|
||||
final MaterialLocalizations localizations;
|
||||
|
||||
@override
|
||||
Future<MaterialLocalizations> load(Locale locale) {
|
||||
return new SynchronousFuture<MaterialLocalizations>(localizations);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
class DefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
|
||||
const DefaultWidgetsLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<WidgetsLocalizations> load(Locale locale) {
|
||||
return new SynchronousFuture<WidgetsLocalizations>(new DefaultWidgetsLocalizations(locale));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReload(DefaultWidgetsLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
@ -3,117 +3,11 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FooMaterialLocalizations extends DefaultMaterialLocalizations {
|
||||
FooMaterialLocalizations(Locale locale) : super(locale);
|
||||
|
||||
@override
|
||||
String get backButtonTooltip => 'foo';
|
||||
}
|
||||
|
||||
class FooMaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
|
||||
const FooMaterialLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<FooMaterialLocalizations> load(Locale locale) {
|
||||
return new SynchronousFuture<FooMaterialLocalizations>(new FooMaterialLocalizations(locale));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReload(FooMaterialLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
/// A localizations delegate that does not contain any useful data, and is only
|
||||
/// used to trigger didChangeDependencies upon locale change.
|
||||
class _DummyLocalizationsDelegate extends LocalizationsDelegate<DummyLocalizations> {
|
||||
@override
|
||||
Future<DummyLocalizations> load(Locale locale) async => new DummyLocalizations();
|
||||
|
||||
@override
|
||||
bool shouldReload(_DummyLocalizationsDelegate old) => true;
|
||||
}
|
||||
|
||||
class DummyLocalizations {}
|
||||
|
||||
Widget buildFrame({
|
||||
Locale locale,
|
||||
Iterable<LocalizationsDelegate<dynamic>> delegates,
|
||||
WidgetBuilder buildContent,
|
||||
LocaleResolutionCallback localeResolutionCallback,
|
||||
Iterable<Locale> supportedLocales: const <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
const Locale('es', 'es'),
|
||||
],
|
||||
}) {
|
||||
return new MaterialApp(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
locale: locale,
|
||||
localizationsDelegates: delegates,
|
||||
localeResolutionCallback: localeResolutionCallback,
|
||||
supportedLocales: supportedLocales,
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return new MaterialPageRoute<Null>(
|
||||
builder: (BuildContext context) {
|
||||
return buildContent(context);
|
||||
}
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('sanity check', (WidgetTester tester) async {
|
||||
final Key textKey = new UniqueKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
buildContent: (BuildContext context) {
|
||||
return new Text(
|
||||
MaterialLocalizations.of(context).backButtonTooltip,
|
||||
key: textKey,
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'Back');
|
||||
|
||||
// Unrecognized locale falls back to 'en'
|
||||
await tester.binding.setLocale('foo', 'bar');
|
||||
await tester.pump();
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'Back');
|
||||
|
||||
// Spanish Bolivia locale, falls back to just 'es'
|
||||
await tester.binding.setLocale('es', 'bo');
|
||||
await tester.pump();
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'Espalda');
|
||||
|
||||
});
|
||||
|
||||
testWidgets('translations exist for all materia/i18n languages', (WidgetTester tester) async {
|
||||
final List<String> languages = <String>[
|
||||
'ar', // Arabic
|
||||
'de', // German
|
||||
'en', // English
|
||||
'es', // Spanish
|
||||
'fa', // Farsi (Persian)
|
||||
'fr', // French
|
||||
'he', // Hebrew
|
||||
'it', // Italian
|
||||
'ja', // Japanese
|
||||
'ps', // Pashto
|
||||
'pt', // Portugese
|
||||
'ru', // Russian
|
||||
'sd', // Sindhi
|
||||
'ur', // Urdu
|
||||
'zh', // Chinese (simplified)
|
||||
];
|
||||
|
||||
for (String language in languages) {
|
||||
final Locale locale = new Locale(language, '');
|
||||
final MaterialLocalizations localizations = new DefaultMaterialLocalizations(locale);
|
||||
testWidgets('English translations exist for all MaterialLocalizations properties', (WidgetTester tester) async {
|
||||
final MaterialLocalizations localizations = const DefaultMaterialLocalizations();
|
||||
|
||||
expect(localizations.openAppDrawerTooltip, isNotNull);
|
||||
expect(localizations.backButtonTooltip, isNotNull);
|
||||
@ -155,171 +49,5 @@ void main() {
|
||||
expect(localizations.pageRowsInfoTitle(1, 10, 100, false).contains(r'$firstRow'), isFalse);
|
||||
expect(localizations.pageRowsInfoTitle(1, 10, 100, false).contains(r'$lastRow'), isFalse);
|
||||
expect(localizations.pageRowsInfoTitle(1, 10, 100, false).contains(r'$rowCount'), isFalse);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('spot check selectedRowCount translations', (WidgetTester tester) async {
|
||||
MaterialLocalizations localizations = new DefaultMaterialLocalizations(const Locale('en', ''));
|
||||
expect(localizations.selectedRowCountTitle(0), 'No items selected');
|
||||
expect(localizations.selectedRowCountTitle(1), '1 item selected');
|
||||
expect(localizations.selectedRowCountTitle(2), '2 items selected');
|
||||
expect(localizations.selectedRowCountTitle(123456789), '123,456,789 items selected');
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('es', ''));
|
||||
expect(localizations.selectedRowCountTitle(0), 'No se han seleccionado elementos');
|
||||
expect(localizations.selectedRowCountTitle(1), '1 artículo seleccionado');
|
||||
expect(localizations.selectedRowCountTitle(2), '2 artículos seleccionados');
|
||||
expect(localizations.selectedRowCountTitle(123456789), '123.456.789 artículos seleccionados');
|
||||
});
|
||||
|
||||
testWidgets('Localizations.override widget tracks parent\'s locale', (WidgetTester tester) async {
|
||||
Widget buildLocaleFrame(Locale locale) {
|
||||
return buildFrame(
|
||||
locale: locale,
|
||||
buildContent: (BuildContext context) {
|
||||
return new Localizations.override(
|
||||
context: context,
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
// No MaterialLocalizations are defined for the first Localizations
|
||||
// ancestor, so we should get the values from the default one, i.e.
|
||||
// the one created by WidgetsApp via the LocalizationsDelegate
|
||||
// provided by MaterialApp.
|
||||
return new Text(MaterialLocalizations.of(context).backButtonTooltip);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US')));
|
||||
expect(find.text('Back'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildLocaleFrame(const Locale('de', 'DE')));
|
||||
expect(find.text('Zurück'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildLocaleFrame(const Locale('zh', 'CN')));
|
||||
expect(find.text('返回'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Localizations.override widget with hardwired locale', (WidgetTester tester) async {
|
||||
Widget buildLocaleFrame(Locale locale) {
|
||||
return buildFrame(
|
||||
locale: locale,
|
||||
buildContent: (BuildContext context) {
|
||||
return new Localizations.override(
|
||||
context: context,
|
||||
locale: const Locale('en', 'US'),
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
// No MaterialLocalizations are defined for the Localizations.override
|
||||
// ancestor, so we should get all values from the default one, i.e.
|
||||
// the one created by WidgetsApp via the LocalizationsDelegate
|
||||
// provided by MaterialApp.
|
||||
return new Text(MaterialLocalizations.of(context).backButtonTooltip);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US')));
|
||||
expect(find.text('Back'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildLocaleFrame(const Locale('de', 'DE')));
|
||||
expect(find.text('Back'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildLocaleFrame(const Locale('zh', 'CN')));
|
||||
expect(find.text('Back'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('MaterialApp overrides MaterialLocalizations', (WidgetTester tester) async {
|
||||
final Key textKey = new UniqueKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
// Accept whatever locale we're given
|
||||
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
|
||||
delegates: <FooMaterialLocalizationsDelegate>[
|
||||
const FooMaterialLocalizationsDelegate(),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
// Should always be 'foo', no matter what the locale is
|
||||
return new Text(
|
||||
MaterialLocalizations.of(context).backButtonTooltip,
|
||||
key: textKey,
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'foo');
|
||||
|
||||
await tester.binding.setLocale('zh', 'CN');
|
||||
await tester.pump();
|
||||
expect(find.text('foo'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocale('de', 'DE');
|
||||
await tester.pump();
|
||||
expect(find.text('foo'), findsOneWidget);
|
||||
|
||||
});
|
||||
|
||||
testWidgets('deprecated Android/Java locales are modernized', (WidgetTester tester) async {
|
||||
final Key textKey = new UniqueKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
supportedLocales: <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
const Locale('he', 'IL'),
|
||||
const Locale('yi', 'IL'),
|
||||
const Locale('id', 'JV'),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
return new Text(
|
||||
'${Localizations.localeOf(context)}',
|
||||
key: textKey,
|
||||
);
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'en_US');
|
||||
|
||||
// Hebrew was iw (ISO-639) is he (ISO-639-1)
|
||||
await tester.binding.setLocale('iw', 'IL');
|
||||
await tester.pump();
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'he_IL');
|
||||
|
||||
// Yiddish was ji (ISO-639) is yi (ISO-639-1)
|
||||
await tester.binding.setLocale('ji', 'IL');
|
||||
await tester.pump();
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'yi_IL');
|
||||
|
||||
// Indonesian was in (ISO-639) is id (ISO-639-1)
|
||||
await tester.binding.setLocale('in', 'JV');
|
||||
await tester.pump();
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'id_JV');
|
||||
});
|
||||
|
||||
testWidgets('Localizations is compatible with ChangeNotifier.dispose() called during didChangeDependencies', (WidgetTester tester) async {
|
||||
// PageView calls ScrollPosition.dispose() during didChangeDependencies.
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
supportedLocales: const <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
const Locale('es', 'ES'),
|
||||
],
|
||||
localizationsDelegates: <_DummyLocalizationsDelegate>[
|
||||
new _DummyLocalizationsDelegate(),
|
||||
],
|
||||
home: new PageView(),
|
||||
));
|
||||
|
||||
await tester.binding.setLocale('es', 'US');
|
||||
await tester.pump();
|
||||
await tester.pumpWidget(new Container());
|
||||
});
|
||||
}
|
||||
|
@ -1,193 +0,0 @@
|
||||
// Copyright 2017 The Chromium 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:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('$MaterialLocalizations localizes text inside the tree', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new ListView(
|
||||
children: <Widget>[
|
||||
new LocalizationTracker(key: const ValueKey<String>('outer')),
|
||||
new Localizations(
|
||||
locale: const Locale('zh', 'CN'),
|
||||
delegates: <LocalizationsDelegate<dynamic>>[
|
||||
new _MaterialLocalizationsDelegate(
|
||||
new DefaultMaterialLocalizations(const Locale('zh', 'CN')),
|
||||
),
|
||||
const DefaultWidgetsLocalizationsDelegate(),
|
||||
],
|
||||
child: new LocalizationTracker(key: const ValueKey<String>('inner')),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
final LocalizationTrackerState outerTracker = tester.state(find.byKey(const ValueKey<String>('outer')));
|
||||
expect(outerTracker.captionFontSize, 12.0);
|
||||
final LocalizationTrackerState innerTracker = tester.state(find.byKey(const ValueKey<String>('inner')));
|
||||
expect(innerTracker.captionFontSize, 13.0);
|
||||
});
|
||||
|
||||
group(DefaultMaterialLocalizations, () {
|
||||
test('uses exact locale when exists', () {
|
||||
final DefaultMaterialLocalizations localizations = new DefaultMaterialLocalizations(const Locale('pt', 'PT'));
|
||||
expect(localizations.formatDecimal(10000), '10\u00A0000');
|
||||
});
|
||||
|
||||
test('falls back to language code when exact locale is missing', () {
|
||||
final DefaultMaterialLocalizations localizations = new DefaultMaterialLocalizations(const Locale('pt', 'XX'));
|
||||
expect(localizations.formatDecimal(10000), '10.000');
|
||||
});
|
||||
|
||||
test('falls back to default format when neither language code nor exact locale are available', () {
|
||||
final DefaultMaterialLocalizations localizations = new DefaultMaterialLocalizations(const Locale('xx', 'XX'));
|
||||
expect(localizations.formatDecimal(10000), '10,000');
|
||||
});
|
||||
|
||||
group('formatHour', () {
|
||||
test('formats h', () {
|
||||
DefaultMaterialLocalizations localizations;
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('en', 'US'));
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 10, minute: 0)), '10');
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '8');
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('ar', ''));
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 10, minute: 0)), '١٠');
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '٨');
|
||||
});
|
||||
|
||||
test('formats HH', () {
|
||||
DefaultMaterialLocalizations localizations;
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('de', ''));
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '09');
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('en', 'GB'));
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '09');
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
|
||||
});
|
||||
|
||||
test('formats H', () {
|
||||
DefaultMaterialLocalizations localizations;
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('es', ''));
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '9');
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('fa', ''));
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '۹');
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '۲۰');
|
||||
});
|
||||
});
|
||||
|
||||
group('formatMinute', () {
|
||||
test('formats English', () {
|
||||
final DefaultMaterialLocalizations localizations = new DefaultMaterialLocalizations(const Locale('en', 'US'));
|
||||
expect(localizations.formatMinute(const TimeOfDay(hour: 1, minute: 32)), '32');
|
||||
});
|
||||
|
||||
test('formats Arabic', () {
|
||||
final DefaultMaterialLocalizations localizations = new DefaultMaterialLocalizations(const Locale('ar', ''));
|
||||
expect(localizations.formatMinute(const TimeOfDay(hour: 1, minute: 32)), '٣٢');
|
||||
});
|
||||
});
|
||||
|
||||
group('formatTimeOfDay', () {
|
||||
test('formats ${TimeOfDayFormat.h_colon_mm_space_a}', () {
|
||||
DefaultMaterialLocalizations localizations;
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('ar', ''));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '٩:٣٢ ص');
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('en', ''));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32 AM');
|
||||
});
|
||||
|
||||
test('formats ${TimeOfDayFormat.HH_colon_mm}', () {
|
||||
DefaultMaterialLocalizations localizations;
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('de', ''));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09:32');
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('en', 'ZA'));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09:32');
|
||||
});
|
||||
|
||||
test('formats ${TimeOfDayFormat.H_colon_mm}', () {
|
||||
DefaultMaterialLocalizations localizations;
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('es', ''));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32');
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('ja', ''));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32');
|
||||
});
|
||||
|
||||
test('formats ${TimeOfDayFormat.frenchCanadian}', () {
|
||||
DefaultMaterialLocalizations localizations;
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('fr', 'CA'));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09 h 32');
|
||||
});
|
||||
|
||||
test('formats ${TimeOfDayFormat.a_space_h_colon_mm}', () {
|
||||
DefaultMaterialLocalizations localizations;
|
||||
|
||||
localizations = new DefaultMaterialLocalizations(const Locale('zh', ''));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '上午 9:32');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class LocalizationTracker extends StatefulWidget {
|
||||
LocalizationTracker({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => new LocalizationTrackerState();
|
||||
}
|
||||
|
||||
class LocalizationTrackerState extends State<LocalizationTracker> {
|
||||
double captionFontSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
captionFontSize = Theme.of(context).textTheme.caption.fontSize;
|
||||
return new Container();
|
||||
}
|
||||
}
|
||||
|
||||
// Same as _MaterialLocalizationsDelegate in widgets/app.dart
|
||||
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
|
||||
const _MaterialLocalizationsDelegate(this.localizations);
|
||||
|
||||
final MaterialLocalizations localizations;
|
||||
|
||||
@override
|
||||
Future<MaterialLocalizations> load(Locale locale) {
|
||||
return new SynchronousFuture<MaterialLocalizations>(localizations);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
// Same as _WidgetsLocalizationsDelegate in widgets/app.dart
|
||||
class DefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
|
||||
const DefaultWidgetsLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<WidgetsLocalizations> load(Locale locale) {
|
||||
return new SynchronousFuture<WidgetsLocalizations>(new DefaultWidgetsLocalizations(locale));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReload(DefaultWidgetsLocalizationsDelegate old) => false;
|
||||
}
|
@ -205,78 +205,4 @@ void main() {
|
||||
expect(feedback.hapticCount, 3);
|
||||
});
|
||||
});
|
||||
|
||||
group('localization', () {
|
||||
testWidgets('can localize the header in all known formats', (WidgetTester tester) async {
|
||||
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
|
||||
final Map<Locale, List<String>> locales = <Locale, List<String>>{
|
||||
const Locale('en', 'US'): const <String>['hour h', 'string :', 'minute', 'period'], //'h:mm a'
|
||||
const Locale('en', 'GB'): const <String>['hour HH', 'string :', 'minute'], //'HH:mm'
|
||||
const Locale('es', 'ES'): const <String>['hour H', 'string :', 'minute'], //'H:mm'
|
||||
const Locale('fr', 'CA'): const <String>['hour HH', 'string h', 'minute'], //'HH \'h\' mm'
|
||||
const Locale('zh', 'ZH'): const <String>['period', 'hour h', 'string :', 'minute'], //'ah:mm'
|
||||
};
|
||||
|
||||
for (Locale locale in locales.keys) {
|
||||
final Offset center = await startPicker(tester, (TimeOfDay time) { }, locale: locale);
|
||||
final List<String> actual = <String>[];
|
||||
tester.element(find.byType(CustomMultiChildLayout)).visitChildren((Element child) {
|
||||
final LayoutId layout = child.widget;
|
||||
final String fragmentType = '${layout.child.runtimeType}';
|
||||
final dynamic widget = layout.child;
|
||||
if (fragmentType == '_MinuteControl') {
|
||||
actual.add('minute');
|
||||
} else if (fragmentType == '_DayPeriodControl') {
|
||||
actual.add('period');
|
||||
} else if (fragmentType == '_HourControl') {
|
||||
actual.add('hour ${widget.hourFormat.toString().split('.').last}');
|
||||
} else if (fragmentType == '_StringFragment') {
|
||||
actual.add('string ${widget.value}');
|
||||
} else {
|
||||
fail('Unsupported fragment type: $fragmentType');
|
||||
}
|
||||
});
|
||||
expect(actual, locales[locale]);
|
||||
await tester.tapAt(new Offset(center.dx, center.dy - 50.0));
|
||||
await finishPicker(tester);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('uses single-ring 12-hour dial for h hour format', (WidgetTester tester) async {
|
||||
// Tap along the segment stretching from the center to the edge at
|
||||
// 12:00 AM position. Because there's only one ring, no matter where you
|
||||
// tap the time will be the same. See the 24-hour dial test that behaves
|
||||
// differently.
|
||||
for (int i = 1; i < 10; i++) {
|
||||
TimeOfDay result;
|
||||
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
||||
final Size size = tester.getSize(find.byKey(const Key('time-picker-dial')));
|
||||
final double dy = (size.height / 2.0 / 10) * i;
|
||||
await tester.tapAt(new Offset(center.dx, center.dy - dy));
|
||||
await finishPicker(tester);
|
||||
expect(result, equals(const TimeOfDay(hour: 0, minute: 0)));
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('uses two-ring 24-hour dial for H and HH hour formats', (WidgetTester tester) async {
|
||||
const List<Locale> locales = const <Locale>[
|
||||
const Locale('en', 'GB'), // HH
|
||||
const Locale('es', 'ES'), // H
|
||||
];
|
||||
for (Locale locale in locales) {
|
||||
// Tap along the segment stretching from the center to the edge at
|
||||
// 12:00 AM position. There are two rings. At ~70% mark, the ring
|
||||
// switches between inner ring and outer ring.
|
||||
for (int i = 1; i < 10; i++) {
|
||||
TimeOfDay result;
|
||||
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; }, locale: locale);
|
||||
final Size size = tester.getSize(find.byKey(const Key('time-picker-dial')));
|
||||
final double dy = (size.height / 2.0 / 10) * i;
|
||||
await tester.tapAt(new Offset(center.dx, center.dy - dy));
|
||||
await finishPicker(tester);
|
||||
expect(result, equals(new TimeOfDay(hour: i < 7 ? 12 : 0, minute: 0)));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ dev_dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
|
@ -0,0 +1,9 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
/// Localizations for the Flutter library
|
||||
library flutter_localizations;
|
||||
|
||||
export 'src/material_localizations.dart' show GlobalMaterialLocalizations;
|
||||
export 'src/widgets_localizations.dart' show GlobalWidgetsLocalizations;
|
@ -1,12 +1,12 @@
|
||||
# Material Library Internationalization
|
||||
# Material Library Localizations
|
||||
|
||||
The `.arb` files in this directory contain localized values (primarily
|
||||
strings) used by the material library. The `localizations.dart` file
|
||||
combines all of the localizations into a single Dart Map that is
|
||||
linked with the rest of the material library.
|
||||
combines all of the localizations into a single Map that is
|
||||
linked with the rest of flutter_localizations package.
|
||||
|
||||
If you're looking for information about internationalizing Flutter
|
||||
apps in general, see th
|
||||
apps in general, see the
|
||||
[Internationalizing Flutter Apps](https://flutter.io/tutorials/internationalization/) tutorial.
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ translation of "CANCEL" which is defined for the `cancelButtonLabel`
|
||||
resource ID.
|
||||
|
||||
Each of the language-specific .arb files contains an entry for
|
||||
`cancelButtonLabel`. They're all represented by the Dart `Map` in the
|
||||
`cancelButtonLabel`. They're all represented by the `Map` in the
|
||||
generated `localizations.dart` file. The Map is used by the
|
||||
MaterialLocalizations class.
|
||||
|
||||
@ -123,18 +123,18 @@ the "Other" suffix. For example the English translations
|
||||
```
|
||||
|
||||
|
||||
### Generated file localizations.dart: all of the localizations as a Dart Map
|
||||
### Generated file localizations.dart: all of the localizations as a Map
|
||||
|
||||
If you look at the comment at the top of `localizations.dart` you'll
|
||||
see that it was manually generated using a `dev/tools` app called
|
||||
`gen_localizations` roughly like this:
|
||||
|
||||
```dart
|
||||
dev/tools/gen_localizations.dart packages/flutter/lib/src/material/i18n material
|
||||
dart dev/tools/gen_localizations.dart packages/flutter_localizations/lib/src/l10n material
|
||||
```
|
||||
|
||||
The gen_localizations app just combines the contents of all of the
|
||||
.arb files into a single Dart `Map` that has entries for each .arb
|
||||
.arb files into a single `Map` that has entries for each .arb
|
||||
file's locale. The `MaterialLocalizations` class implementation uses
|
||||
this Map to implement the methods that lookup localized resource
|
||||
values.
|
@ -4,7 +4,7 @@
|
||||
|
||||
// This file has been automatically generated. Please do not edit it manually.
|
||||
// To regenerate the file, use:
|
||||
// dart dev/tools/gen_localizations.dart packages/flutter/lib/src/material/i18n material
|
||||
// dart dev/tools/gen_localizations.dart packages/flutter_localizations/lib/src/l10n material
|
||||
|
||||
/// Maps from [Locale.languageCode] to a map that contains the localized strings
|
||||
/// for that locale.
|
||||
@ -452,4 +452,3 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
|
||||
"postMeridiemAbbreviation": r"下午",
|
||||
},
|
||||
};
|
||||
|
@ -0,0 +1,431 @@
|
||||
// Copyright 2017 The Chromium 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 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'package:intl/date_symbol_data_local.dart' as intl_local_date_data;
|
||||
|
||||
import 'l10n/localizations.dart';
|
||||
import 'widgets_localizations.dart';
|
||||
|
||||
/// Localized strings for the material widgets.
|
||||
///
|
||||
/// 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
|
||||
/// new MaterialApp(
|
||||
/// localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
/// supportedLocales: [
|
||||
/// const Locale('en', 'US'), // English
|
||||
/// const Locale('he', 'IL'), // Hebrew
|
||||
/// // ...
|
||||
/// ],
|
||||
/// // ...
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// This class supports locales with the following [Locale.languageCode]s:
|
||||
///
|
||||
/// * ar - Arabic
|
||||
/// * de - German
|
||||
/// * en - English
|
||||
/// * es - Spanish
|
||||
/// * fa - Farsi
|
||||
/// * fr - French
|
||||
/// * he - Hebrew
|
||||
/// * it - Italian
|
||||
/// * ja - Japanese
|
||||
/// * ps - Pashto
|
||||
/// * pt - Portugese
|
||||
/// * ru - Russian
|
||||
/// * sd - Sindhi
|
||||
/// * ur - Urdu
|
||||
/// * zh - Simplified Chinese
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * The Flutter Internationalization Tutorial,
|
||||
/// <https://flutter.io/tutorials/internationalization/>.
|
||||
/// * [DefaultMaterialLocalizations], which only provides US English translations.
|
||||
class GlobalMaterialLocalizations implements MaterialLocalizations {
|
||||
/// Constructs an object that defines the material widgets' localized strings
|
||||
/// for the given `locale`.
|
||||
///
|
||||
/// [LocalizationsDelegate] implementations typically call the static [load]
|
||||
/// function, rather than constructing this class directly.
|
||||
GlobalMaterialLocalizations(this.locale)
|
||||
: assert(locale != null),
|
||||
this._localeName = _computeLocaleName(locale) {
|
||||
_loadDateIntlDataIfNotLoaded();
|
||||
|
||||
if (localizations.containsKey(locale.languageCode))
|
||||
_nameToValue.addAll(localizations[locale.languageCode]);
|
||||
if (localizations.containsKey(_localeName))
|
||||
_nameToValue.addAll(localizations[_localeName]);
|
||||
|
||||
const String kMediumDatePattern = 'E, MMM\u00a0d';
|
||||
if (intl.DateFormat.localeExists(_localeName)) {
|
||||
_fullYearFormat = new intl.DateFormat.y(_localeName);
|
||||
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern, _localeName);
|
||||
_yearMonthFormat = new intl.DateFormat('yMMMM', _localeName);
|
||||
} else if (intl.DateFormat.localeExists(locale.languageCode)) {
|
||||
_fullYearFormat = new intl.DateFormat.y(locale.languageCode);
|
||||
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern, locale.languageCode);
|
||||
_yearMonthFormat = new intl.DateFormat('yMMMM', locale.languageCode);
|
||||
} else {
|
||||
_fullYearFormat = new intl.DateFormat.y();
|
||||
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern);
|
||||
_yearMonthFormat = new intl.DateFormat('yMMMM');
|
||||
}
|
||||
|
||||
if (intl.NumberFormat.localeExists(_localeName)) {
|
||||
_decimalFormat = new intl.NumberFormat.decimalPattern(_localeName);
|
||||
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00', _localeName);
|
||||
} else if (intl.NumberFormat.localeExists(locale.languageCode)) {
|
||||
_decimalFormat = new intl.NumberFormat.decimalPattern(locale.languageCode);
|
||||
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00', locale.languageCode);
|
||||
} else {
|
||||
_decimalFormat = new intl.NumberFormat.decimalPattern();
|
||||
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00');
|
||||
}
|
||||
}
|
||||
|
||||
/// The locale for which the values of this class's localized resources
|
||||
/// have been translated.
|
||||
final Locale locale;
|
||||
|
||||
final String _localeName;
|
||||
|
||||
final Map<String, String> _nameToValue = <String, String>{};
|
||||
|
||||
intl.NumberFormat _decimalFormat;
|
||||
|
||||
intl.NumberFormat _twoDigitZeroPaddedFormat;
|
||||
|
||||
intl.DateFormat _fullYearFormat;
|
||||
|
||||
intl.DateFormat _mediumDateFormat;
|
||||
|
||||
intl.DateFormat _yearMonthFormat;
|
||||
|
||||
static String _computeLocaleName(Locale locale) {
|
||||
final String localeName = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
|
||||
return intl.Intl.canonicalizedLocale(localeName);
|
||||
}
|
||||
|
||||
// TODO(hmuller): the rules for mapping from an integer value to
|
||||
// "one" or "two" etc. are locale specific and an additional "few" category
|
||||
// is needed. See http://cldr.unicode.org/index/cldr-spec/plural-rules
|
||||
String _nameToPluralValue(int count, String key) {
|
||||
String text;
|
||||
if (count == 0)
|
||||
text = _nameToValue['${key}Zero'];
|
||||
else if (count == 1)
|
||||
text = _nameToValue['${key}One'];
|
||||
else if (count == 2)
|
||||
text = _nameToValue['${key}Two'];
|
||||
else if (count > 2)
|
||||
text = _nameToValue['${key}Many'];
|
||||
text ??= _nameToValue['${key}Other'];
|
||||
assert(text != null);
|
||||
return text;
|
||||
}
|
||||
|
||||
@override
|
||||
String formatHour(TimeOfDay timeOfDay) {
|
||||
switch (hourFormat(of: timeOfDayFormat)) {
|
||||
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);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String formatMinute(TimeOfDay timeOfDay) {
|
||||
return _twoDigitZeroPaddedFormat.format(timeOfDay.minute);
|
||||
}
|
||||
|
||||
@override
|
||||
String formatYear(DateTime date) {
|
||||
return _fullYearFormat.format(date);
|
||||
}
|
||||
|
||||
@override
|
||||
String formatMediumDate(DateTime date) {
|
||||
return _mediumDateFormat.format(date);
|
||||
}
|
||||
|
||||
@override
|
||||
String formatMonthYear(DateTime date) {
|
||||
return _yearMonthFormat.format(date);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get narrowWeekdays {
|
||||
return _fullYearFormat.dateSymbols.NARROWWEEKDAYS;
|
||||
}
|
||||
|
||||
@override
|
||||
int get firstDayOfWeekIndex => (_fullYearFormat.dateSymbols.FIRSTDAYOFWEEK + 1) % 7;
|
||||
|
||||
/// Formats a [number] using local decimal number format.
|
||||
///
|
||||
/// Inserts locale-appropriate thousands separator, if necessary.
|
||||
String formatDecimal(int number) {
|
||||
return _decimalFormat.format(number);
|
||||
}
|
||||
|
||||
@override
|
||||
String formatTimeOfDay(TimeOfDay timeOfDay) {
|
||||
// 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.
|
||||
switch (timeOfDayFormat) {
|
||||
case TimeOfDayFormat.h_colon_mm_space_a:
|
||||
return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)} ${_formatDayPeriod(timeOfDay)}';
|
||||
case TimeOfDayFormat.H_colon_mm:
|
||||
case TimeOfDayFormat.HH_colon_mm:
|
||||
return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)}';
|
||||
case TimeOfDayFormat.HH_dot_mm:
|
||||
return '${formatHour(timeOfDay)}.${formatMinute(timeOfDay)}';
|
||||
case TimeOfDayFormat.a_space_h_colon_mm:
|
||||
return '${_formatDayPeriod(timeOfDay)} ${formatHour(timeOfDay)}:${formatMinute(timeOfDay)}';
|
||||
case TimeOfDayFormat.frenchCanadian:
|
||||
return '${formatHour(timeOfDay)} h ${formatMinute(timeOfDay)}';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String _formatDayPeriod(TimeOfDay timeOfDay) {
|
||||
switch (timeOfDay.period) {
|
||||
case DayPeriod.am:
|
||||
return anteMeridiemAbbreviation;
|
||||
case DayPeriod.pm:
|
||||
return postMeridiemAbbreviation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String get openAppDrawerTooltip => _nameToValue['openAppDrawerTooltip'];
|
||||
|
||||
@override
|
||||
String get backButtonTooltip => _nameToValue['backButtonTooltip'];
|
||||
|
||||
@override
|
||||
String get closeButtonTooltip => _nameToValue['closeButtonTooltip'];
|
||||
|
||||
@override
|
||||
String get nextMonthTooltip => _nameToValue['nextMonthTooltip'];
|
||||
|
||||
@override
|
||||
String get previousMonthTooltip => _nameToValue['previousMonthTooltip'];
|
||||
|
||||
@override
|
||||
String get nextPageTooltip => _nameToValue['nextPageTooltip'];
|
||||
|
||||
@override
|
||||
String get previousPageTooltip => _nameToValue['previousPageTooltip'];
|
||||
|
||||
@override
|
||||
String get showMenuTooltip => _nameToValue['showMenuTooltip'];
|
||||
|
||||
@override
|
||||
String aboutListTileTitle(String applicationName) {
|
||||
final String text = _nameToValue['aboutListTileTitle'];
|
||||
return text.replaceFirst(r'$applicationName', applicationName);
|
||||
}
|
||||
|
||||
@override
|
||||
String get licensesPageTitle => _nameToValue['licensesPageTitle'];
|
||||
|
||||
@override
|
||||
String pageRowsInfoTitle(int firstRow, int lastRow, int rowCount, bool rowCountIsApproximate) {
|
||||
String text = rowCountIsApproximate ? _nameToValue['pageRowsInfoTitleApproximate'] : null;
|
||||
text ??= _nameToValue['pageRowsInfoTitle'];
|
||||
assert(text != null, 'A $locale localization was not found for pageRowsInfoTitle or pageRowsInfoTitleApproximate');
|
||||
// TODO(hansmuller): this could be more efficient.
|
||||
return text
|
||||
.replaceFirst(r'$firstRow', formatDecimal(firstRow))
|
||||
.replaceFirst(r'$lastRow', formatDecimal(lastRow))
|
||||
.replaceFirst(r'$rowCount', formatDecimal(rowCount));
|
||||
}
|
||||
|
||||
@override
|
||||
String get rowsPerPageTitle => _nameToValue['rowsPerPageTitle'];
|
||||
|
||||
@override
|
||||
String selectedRowCountTitle(int selectedRowCount) {
|
||||
return _nameToPluralValue(selectedRowCount, 'selectedRowCountTitle') // asserts on no match
|
||||
.replaceFirst(r'$selectedRowCount', formatDecimal(selectedRowCount));
|
||||
}
|
||||
|
||||
@override
|
||||
String get cancelButtonLabel => _nameToValue['cancelButtonLabel'];
|
||||
|
||||
@override
|
||||
String get closeButtonLabel => _nameToValue['closeButtonLabel'];
|
||||
|
||||
@override
|
||||
String get continueButtonLabel => _nameToValue['continueButtonLabel'];
|
||||
|
||||
@override
|
||||
String get copyButtonLabel => _nameToValue['copyButtonLabel'];
|
||||
|
||||
@override
|
||||
String get cutButtonLabel => _nameToValue['cutButtonLabel'];
|
||||
|
||||
@override
|
||||
String get okButtonLabel => _nameToValue['okButtonLabel'];
|
||||
|
||||
@override
|
||||
String get pasteButtonLabel => _nameToValue['pasteButtonLabel'];
|
||||
|
||||
@override
|
||||
String get selectAllButtonLabel => _nameToValue['selectAllButtonLabel'];
|
||||
|
||||
@override
|
||||
String get viewLicensesButtonLabel => _nameToValue['viewLicensesButtonLabel'];
|
||||
|
||||
@override
|
||||
String get anteMeridiemAbbreviation => _nameToValue['anteMeridiemAbbreviation'];
|
||||
|
||||
@override
|
||||
String get postMeridiemAbbreviation => _nameToValue['postMeridiemAbbreviation'];
|
||||
|
||||
/// 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 shows the
|
||||
/// short time pattern used in locale en_US
|
||||
@override
|
||||
TimeOfDayFormat get timeOfDayFormat {
|
||||
final String icuShortTimePattern = _nameToValue['timeOfDayFormat'];
|
||||
|
||||
assert(() {
|
||||
if (!_icuTimeOfDayToEnum.containsKey(icuShortTimePattern)) {
|
||||
throw new FlutterError(
|
||||
'"$icuShortTimePattern" is not one of the ICU short time patterns '
|
||||
'supported by the material library. Here is the list of supported '
|
||||
'patterns:\n ' +
|
||||
_icuTimeOfDayToEnum.keys.join('\n ')
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
|
||||
return _icuTimeOfDayToEnum[icuShortTimePattern];
|
||||
}
|
||||
|
||||
/// Looks up text geometry defined in [MaterialTextGeometry].
|
||||
@override
|
||||
TextTheme get localTextGeometry => MaterialTextGeometry.forScriptCategory(_nameToValue["scriptCategory"]);
|
||||
|
||||
/// Creates an object that provides localized resource values for the
|
||||
/// for the widgets of the material library.
|
||||
///
|
||||
/// This method is typically used to create a [LocalizationsDelegate].
|
||||
/// The [MaterialApp] does so by default.
|
||||
static Future<MaterialLocalizations> load(Locale locale) {
|
||||
return new SynchronousFuture<MaterialLocalizations>(new GlobalMaterialLocalizations(locale));
|
||||
}
|
||||
|
||||
/// A [LocalizationsDelegate] that uses [GlobalMaterialLocalizations.load]
|
||||
/// to create an instance of this class.
|
||||
///
|
||||
/// Most internationlized 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 = const _MaterialLocalizationsDelegate();
|
||||
|
||||
/// A value for [MaterialApp.localizationsDelegates] that's typically used by
|
||||
/// internationalized apps.
|
||||
///
|
||||
/// 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
|
||||
/// new MaterialApp(
|
||||
/// localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
/// supportedLocales: [
|
||||
/// const Locale('en', 'US'), // English
|
||||
/// const Locale('he', 'IL'), // Hebrew
|
||||
/// ],
|
||||
/// // ...
|
||||
/// )
|
||||
/// ```
|
||||
static const List<LocalizationsDelegate<dynamic>> delegates = const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
];
|
||||
}
|
||||
|
||||
const Map<String, TimeOfDayFormat> _icuTimeOfDayToEnum = const <String, TimeOfDayFormat>{
|
||||
'HH:mm': TimeOfDayFormat.HH_colon_mm,
|
||||
'HH.mm': TimeOfDayFormat.HH_dot_mm,
|
||||
"HH 'h' mm": TimeOfDayFormat.frenchCanadian,
|
||||
'HH:mm น.': TimeOfDayFormat.HH_colon_mm,
|
||||
'H:mm': TimeOfDayFormat.H_colon_mm,
|
||||
'h:mm a': TimeOfDayFormat.h_colon_mm_space_a,
|
||||
'a h:mm': TimeOfDayFormat.a_space_h_colon_mm,
|
||||
'ah:mm': TimeOfDayFormat.a_space_h_colon_mm,
|
||||
};
|
||||
|
||||
/// Tracks if date i18n data has been loaded.
|
||||
bool _dateIntlDataInitialized = false;
|
||||
|
||||
/// Loads i18n data for dates if it hasn't be loaded yet.
|
||||
///
|
||||
/// Only the first invocation of this function has the effect of loading the
|
||||
/// data. Subsequent invocations have no effect.
|
||||
void _loadDateIntlDataIfNotLoaded() {
|
||||
if (!_dateIntlDataInitialized) {
|
||||
// The returned Future is intentionally dropped on the floor. The
|
||||
// function only returns it to be compatible with the async counterparts.
|
||||
// The Future has no value otherwise.
|
||||
intl_local_date_data.initializeDateFormatting();
|
||||
_dateIntlDataInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
|
||||
const _MaterialLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<MaterialLocalizations> load(Locale locale) => GlobalMaterialLocalizations.load(locale);
|
||||
|
||||
@override
|
||||
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
// Copyright 2017 The Chromium 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 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/// Localized values for widgets.
|
||||
///
|
||||
/// Currently this class just maps [locale] to [textDirection]. All locales
|
||||
/// are [TextDirection.ltr] except for locales with the following
|
||||
/// [Locale.languageCode] values, which are [TextDirection.rtl]:
|
||||
///
|
||||
/// * ar - Arabic
|
||||
/// * fa - Farsi
|
||||
/// * he - Hebrew
|
||||
/// * ps - Pashto
|
||||
/// * sd - Sindhi
|
||||
/// * ur - Urdu
|
||||
class GlobalWidgetsLocalizations implements WidgetsLocalizations {
|
||||
/// Construct an object that defines the localized values for the widgets
|
||||
/// library for the given `locale`.
|
||||
///
|
||||
/// [LocalizationsDelegate] implementations typically call the static [load]
|
||||
/// function, rather than constructing this class directly.
|
||||
GlobalWidgetsLocalizations(this.locale) {
|
||||
final String language = locale.languageCode.toLowerCase();
|
||||
_textDirection = _rtlLanguages.contains(language) ? TextDirection.rtl : TextDirection.ltr;
|
||||
}
|
||||
|
||||
// See http://en.wikipedia.org/wiki/Right-to-left
|
||||
static const List<String> _rtlLanguages = const <String>[
|
||||
'ar', // Arabic
|
||||
'fa', // Farsi
|
||||
'he', // Hebrew
|
||||
'ps', // Pashto
|
||||
'sd', // Sindhi
|
||||
'ur', // Urdu
|
||||
];
|
||||
|
||||
/// The locale for which the values of this class's localized resources
|
||||
/// have been translated.
|
||||
final Locale locale;
|
||||
|
||||
@override
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
|
||||
/// Creates an object that provides localized resource values for the
|
||||
/// lowest levels of the Flutter framework.
|
||||
///
|
||||
/// This method is typically used to create a [LocalizationsDelegate].
|
||||
/// The [WidgetsApp] does so by default.
|
||||
static Future<WidgetsLocalizations> load(Locale locale) {
|
||||
return new SynchronousFuture<WidgetsLocalizations>(new GlobalWidgetsLocalizations(locale));
|
||||
}
|
||||
|
||||
/// A [LocalizationsDelegate] that uses [GlobalWidgetsLocalizations.load]
|
||||
/// to create an instance of this class.
|
||||
///
|
||||
/// [WidgetsApp] automatically adds this value to [WidgetApp.localizationsDelegates].
|
||||
static const LocalizationsDelegate<WidgetsLocalizations> delegate = const _WidgetsLocalizationsDelegate();
|
||||
}
|
||||
|
||||
class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
|
||||
const _WidgetsLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<WidgetsLocalizations> load(Locale locale) => GlobalWidgetsLocalizations.load(locale);
|
||||
|
||||
@override
|
||||
bool shouldReload(_WidgetsLocalizationsDelegate old) => false;
|
||||
}
|
63
packages/flutter_localizations/pubspec.yaml
Normal file
63
packages/flutter_localizations/pubspec.yaml
Normal file
@ -0,0 +1,63 @@
|
||||
name: flutter_localizations
|
||||
version: 0.0.1-dev
|
||||
dependencies:
|
||||
# To update these, use "flutter update-packages --force-upgrade".
|
||||
|
||||
flutter:
|
||||
sdk: flutter
|
||||
intl: 0.15.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
mockito: 2.2.0
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||
convert: 2.0.1 # TRANSITIVE DEPENDENCY
|
||||
crypto: 2.0.2+1 # TRANSITIVE DEPENDENCY
|
||||
csslib: 0.14.1 # TRANSITIVE DEPENDENCY
|
||||
dart_style: 1.0.8 # TRANSITIVE DEPENDENCY
|
||||
glob: 1.1.5 # TRANSITIVE DEPENDENCY
|
||||
html: 0.13.2 # TRANSITIVE DEPENDENCY
|
||||
http: 0.11.3+14 # TRANSITIVE DEPENDENCY
|
||||
http_multi_server: 2.0.4 # TRANSITIVE DEPENDENCY
|
||||
http_parser: 3.1.1 # TRANSITIVE DEPENDENCY
|
||||
intl_translation: 0.15.0 # TRANSITIVE DEPENDENCY
|
||||
isolate: 1.1.0 # TRANSITIVE DEPENDENCY
|
||||
js: 0.6.1 # TRANSITIVE DEPENDENCY
|
||||
logging: 0.11.3+1 # TRANSITIVE DEPENDENCY
|
||||
matcher: 0.12.1+4 # TRANSITIVE DEPENDENCY
|
||||
meta: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
mime: 0.9.3 # TRANSITIVE DEPENDENCY
|
||||
node_preamble: 1.4.0 # TRANSITIVE DEPENDENCY
|
||||
package_config: 1.0.3 # TRANSITIVE DEPENDENCY
|
||||
package_resolver: 1.0.2 # TRANSITIVE DEPENDENCY
|
||||
path: 1.4.2 # TRANSITIVE DEPENDENCY
|
||||
petitparser: 1.6.1 # TRANSITIVE DEPENDENCY
|
||||
plugin: 0.2.0+2 # TRANSITIVE DEPENDENCY
|
||||
pool: 1.3.3 # TRANSITIVE DEPENDENCY
|
||||
pub_semver: 1.3.2 # TRANSITIVE DEPENDENCY
|
||||
shelf: 0.7.0 # TRANSITIVE DEPENDENCY
|
||||
shelf_packages_handler: 1.0.3 # TRANSITIVE DEPENDENCY
|
||||
shelf_static: 0.2.5 # TRANSITIVE DEPENDENCY
|
||||
shelf_web_socket: 0.2.2 # TRANSITIVE DEPENDENCY
|
||||
source_map_stack_trace: 1.1.4 # TRANSITIVE DEPENDENCY
|
||||
source_maps: 0.10.4 # TRANSITIVE DEPENDENCY
|
||||
source_span: 1.4.0 # TRANSITIVE DEPENDENCY
|
||||
stack_trace: 1.8.2 # TRANSITIVE DEPENDENCY
|
||||
stream_channel: 1.6.2 # TRANSITIVE DEPENDENCY
|
||||
string_scanner: 1.0.2 # TRANSITIVE DEPENDENCY
|
||||
term_glyph: 1.0.0 # TRANSITIVE DEPENDENCY
|
||||
test: 0.12.24+8 # TRANSITIVE DEPENDENCY
|
||||
typed_data: 1.1.4 # TRANSITIVE DEPENDENCY
|
||||
utf: 0.9.0+3 # TRANSITIVE DEPENDENCY
|
||||
vector_math: 2.0.5 # TRANSITIVE DEPENDENCY
|
||||
watcher: 0.9.7+4 # TRANSITIVE DEPENDENCY
|
||||
web_socket_channel: 1.0.6 # TRANSITIVE DEPENDENCY
|
||||
yaml: 2.1.13 # TRANSITIVE DEPENDENCY
|
79
packages/flutter_localizations/test/basics_test.dart
Normal file
79
packages/flutter_localizations/test/basics_test.dart
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2016 The Chromium 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/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Nested Localizations', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(new MaterialApp( // Creates the outer Localizations widget.
|
||||
home: new ListView(
|
||||
children: <Widget>[
|
||||
new LocalizationTracker(key: const ValueKey<String>('outer')),
|
||||
new Localizations(
|
||||
locale: const Locale('zh', 'CN'),
|
||||
delegates: GlobalMaterialLocalizations.delegates,
|
||||
child: new LocalizationTracker(key: const ValueKey<String>('inner')),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
final LocalizationTrackerState outerTracker = tester.state(find.byKey(const ValueKey<String>('outer')));
|
||||
expect(outerTracker.captionFontSize, 12.0);
|
||||
final LocalizationTrackerState innerTracker = tester.state(find.byKey(const ValueKey<String>('inner')));
|
||||
expect(innerTracker.captionFontSize, 13.0);
|
||||
});
|
||||
|
||||
testWidgets('Localizations is compatible with ChangeNotifier.dispose() called during didChangeDependencies', (WidgetTester tester) async {
|
||||
// PageView calls ScrollPosition.dispose() during didChangeDependencies.
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
supportedLocales: const <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
const Locale('es', 'ES'),
|
||||
],
|
||||
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
|
||||
new _DummyLocalizationsDelegate(),
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
],
|
||||
home: new PageView(),
|
||||
)
|
||||
);
|
||||
|
||||
await tester.binding.setLocale('es', 'US');
|
||||
await tester.pump();
|
||||
await tester.pumpWidget(new Container());
|
||||
});
|
||||
}
|
||||
|
||||
/// A localizations delegate that does not contain any useful data, and is only
|
||||
/// used to trigger didChangeDependencies upon locale change.
|
||||
class _DummyLocalizationsDelegate extends LocalizationsDelegate<DummyLocalizations> {
|
||||
@override
|
||||
Future<DummyLocalizations> load(Locale locale) async => new DummyLocalizations();
|
||||
|
||||
@override
|
||||
bool shouldReload(_DummyLocalizationsDelegate old) => true;
|
||||
}
|
||||
|
||||
class DummyLocalizations {}
|
||||
|
||||
class LocalizationTracker extends StatefulWidget {
|
||||
LocalizationTracker({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => new LocalizationTrackerState();
|
||||
}
|
||||
|
||||
class LocalizationTrackerState extends State<LocalizationTracker> {
|
||||
double captionFontSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
captionFontSize = Theme.of(context).textTheme.caption.fontSize;
|
||||
return new Container();
|
||||
}
|
||||
}
|
236
packages/flutter_localizations/test/date_picker_test.dart
Normal file
236
packages/flutter_localizations/test/date_picker_test.dart
Normal file
@ -0,0 +1,236 @@
|
||||
// Copyright 2016 The Chromium 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/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
DateTime firstDate;
|
||||
DateTime lastDate;
|
||||
DateTime initialDate;
|
||||
|
||||
setUp(() {
|
||||
firstDate = new DateTime(2001, DateTime.JANUARY, 1);
|
||||
lastDate = new DateTime(2031, DateTime.DECEMBER, 31);
|
||||
initialDate = new DateTime(2016, DateTime.JANUARY, 15);
|
||||
});
|
||||
|
||||
group(DayPicker, () {
|
||||
final Map<Locale, Map<String, dynamic>> testLocales = <Locale, Map<String, dynamic>>{
|
||||
// Tests the default.
|
||||
const Locale('en', 'US'): <String, dynamic>{
|
||||
'textDirection': TextDirection.ltr,
|
||||
'expectedDaysOfWeek': <String>['S', 'M', 'T', 'W', 'T', 'F', 'S'],
|
||||
'expectedDaysOfMonth': new List<String>.generate(30, (int i) => '${i + 1}'),
|
||||
'expectedMonthYearHeader': 'September 2017',
|
||||
},
|
||||
// Tests a different first day of week.
|
||||
const Locale('ru', 'RU'): <String, dynamic>{
|
||||
'textDirection': TextDirection.ltr,
|
||||
'expectedDaysOfWeek': <String>['пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс'],
|
||||
'expectedDaysOfMonth': new List<String>.generate(30, (int i) => '${i + 1}'),
|
||||
'expectedMonthYearHeader': 'сентябрь 2017 г.',
|
||||
},
|
||||
// Tests RTL.
|
||||
// TODO: change to Arabic numerals when these are fixed:
|
||||
// TODO: https://github.com/dart-lang/intl/issues/143
|
||||
// TODO: https://github.com/flutter/flutter/issues/12289
|
||||
const Locale('ar', 'AR'): <String, dynamic>{
|
||||
'textDirection': TextDirection.rtl,
|
||||
'expectedDaysOfWeek': <String>['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'],
|
||||
'expectedDaysOfMonth': new List<String>.generate(30, (int i) => '${i + 1}'),
|
||||
'expectedMonthYearHeader': 'سبتمبر 2017',
|
||||
},
|
||||
};
|
||||
|
||||
for (Locale locale in testLocales.keys) {
|
||||
testWidgets('shows dates for $locale', (WidgetTester tester) async {
|
||||
final List<String> expectedDaysOfWeek = testLocales[locale]['expectedDaysOfWeek'];
|
||||
final List<String> expectedDaysOfMonth = testLocales[locale]['expectedDaysOfMonth'];
|
||||
final String expectedMonthYearHeader = testLocales[locale]['expectedMonthYearHeader'];
|
||||
final TextDirection textDirection = testLocales[locale]['textDirection'];
|
||||
final DateTime baseDate = new DateTime(2017, 9, 27);
|
||||
|
||||
await _pumpBoilerplate(tester, new DayPicker(
|
||||
selectedDate: baseDate,
|
||||
currentDate: baseDate,
|
||||
onChanged: (DateTime newValue) {},
|
||||
firstDate: baseDate.subtract(const Duration(days: 90)),
|
||||
lastDate: baseDate.add(const Duration(days: 90)),
|
||||
displayedMonth: baseDate,
|
||||
), locale: locale, textDirection: textDirection);
|
||||
|
||||
expect(find.text(expectedMonthYearHeader), findsOneWidget);
|
||||
|
||||
expectedDaysOfWeek.forEach((String dayOfWeek) {
|
||||
expect(find.text(dayOfWeek), findsWidgets);
|
||||
});
|
||||
|
||||
Offset previousCellOffset;
|
||||
expectedDaysOfMonth.forEach((String dayOfMonth) {
|
||||
final Finder dayCell = find.descendant(of: find.byType(GridView), matching: find.text(dayOfMonth));
|
||||
expect(dayCell, findsOneWidget);
|
||||
|
||||
// Check that cells are correctly positioned relative to each other,
|
||||
// taking text direction into account.
|
||||
final Offset offset = tester.getCenter(dayCell);
|
||||
if (previousCellOffset != null) {
|
||||
if (textDirection == TextDirection.ltr) {
|
||||
expect(offset.dx > previousCellOffset.dx && offset.dy == previousCellOffset.dy || offset.dy > previousCellOffset.dy, true);
|
||||
} else {
|
||||
expect(offset.dx < previousCellOffset.dx && offset.dy == previousCellOffset.dy || offset.dy > previousCellOffset.dy, true);
|
||||
}
|
||||
}
|
||||
previousCellOffset = offset;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('locale parameter overrides ambient locale', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
locale: const Locale('en', 'US'),
|
||||
supportedLocales: const <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
const Locale('fr', 'CA'),
|
||||
],
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
home: new Material(
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return new FlatButton(
|
||||
onPressed: () async {
|
||||
await showDatePicker(
|
||||
context: context,
|
||||
initialDate: initialDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
locale: const Locale('fr', 'CA'),
|
||||
);
|
||||
},
|
||||
child: const Text('X'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.text('X'));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
final Element dayPicker = tester.element(find.byType(DayPicker));
|
||||
expect(
|
||||
Localizations.localeOf(dayPicker),
|
||||
const Locale('fr', 'CA'),
|
||||
);
|
||||
|
||||
expect(
|
||||
Directionality.of(dayPicker),
|
||||
TextDirection.ltr,
|
||||
);
|
||||
|
||||
await tester.tap(find.text('ANNULER'));
|
||||
});
|
||||
|
||||
testWidgets('textDirection parameter overrides ambient textDirection', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
locale: const Locale('en', 'US'),
|
||||
supportedLocales: const <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
],
|
||||
home: new Material(
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return new FlatButton(
|
||||
onPressed: () async {
|
||||
await showDatePicker(
|
||||
context: context,
|
||||
initialDate: initialDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
textDirection: TextDirection.rtl,
|
||||
);
|
||||
},
|
||||
child: const Text('X'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.text('X'));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
final Element dayPicker = tester.element(find.byType(DayPicker));
|
||||
expect(
|
||||
Directionality.of(dayPicker),
|
||||
TextDirection.rtl,
|
||||
);
|
||||
|
||||
await tester.tap(find.text('CANCEL'));
|
||||
});
|
||||
|
||||
testWidgets('textDirection parameter takes precendence over locale parameter', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
locale: const Locale('en', 'US'),
|
||||
supportedLocales: const <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
const Locale('fr', 'CA'),
|
||||
],
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
home: new Material(
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return new FlatButton(
|
||||
onPressed: () async {
|
||||
await showDatePicker(
|
||||
context: context,
|
||||
initialDate: initialDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
locale: const Locale('fr', 'CA'),
|
||||
textDirection: TextDirection.rtl,
|
||||
);
|
||||
},
|
||||
child: const Text('X'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.text('X'));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
final Element dayPicker = tester.element(find.byType(DayPicker));
|
||||
expect(
|
||||
Localizations.localeOf(dayPicker),
|
||||
const Locale('fr', 'CA'),
|
||||
);
|
||||
|
||||
expect(
|
||||
Directionality.of(dayPicker),
|
||||
TextDirection.rtl,
|
||||
);
|
||||
|
||||
await tester.tap(find.text('ANNULER'));
|
||||
});
|
||||
}
|
||||
|
||||
Future<Null> _pumpBoilerplate(
|
||||
WidgetTester tester,
|
||||
Widget child, {
|
||||
Locale locale = const Locale('en', 'US'),
|
||||
TextDirection textDirection: TextDirection.ltr
|
||||
}) async {
|
||||
await tester.pumpWidget(new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Localizations(
|
||||
locale: locale,
|
||||
delegates: GlobalMaterialLocalizations.delegates,
|
||||
child: child,
|
||||
),
|
||||
));
|
||||
}
|
122
packages/flutter_localizations/test/date_time_test.dart
Normal file
122
packages/flutter_localizations/test/date_time_test.dart
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright 2017 The Chromium 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/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group(GlobalMaterialLocalizations, () {
|
||||
test('uses exact locale when exists', () {
|
||||
final GlobalMaterialLocalizations localizations = new GlobalMaterialLocalizations(const Locale('pt', 'PT'));
|
||||
expect(localizations.formatDecimal(10000), '10\u00A0000');
|
||||
});
|
||||
|
||||
test('falls back to language code when exact locale is missing', () {
|
||||
final GlobalMaterialLocalizations localizations = new GlobalMaterialLocalizations(const Locale('pt', 'XX'));
|
||||
expect(localizations.formatDecimal(10000), '10.000');
|
||||
});
|
||||
|
||||
test('falls back to default format when neither language code nor exact locale are available', () {
|
||||
final GlobalMaterialLocalizations localizations = new GlobalMaterialLocalizations(const Locale('xx', 'XX'));
|
||||
expect(localizations.formatDecimal(10000), '10,000');
|
||||
});
|
||||
|
||||
group('formatHour', () {
|
||||
test('formats h', () {
|
||||
GlobalMaterialLocalizations localizations;
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('en', 'US'));
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 10, minute: 0)), '10');
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '8');
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('ar', ''));
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 10, minute: 0)), '١٠');
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '٨');
|
||||
});
|
||||
|
||||
test('formats HH', () {
|
||||
GlobalMaterialLocalizations localizations;
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('de', ''));
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '09');
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('en', 'GB'));
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '09');
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
|
||||
});
|
||||
|
||||
test('formats H', () {
|
||||
GlobalMaterialLocalizations localizations;
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('es', ''));
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '9');
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('fa', ''));
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '۹');
|
||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '۲۰');
|
||||
});
|
||||
});
|
||||
|
||||
group('formatMinute', () {
|
||||
test('formats English', () {
|
||||
final GlobalMaterialLocalizations localizations = new GlobalMaterialLocalizations(const Locale('en', 'US'));
|
||||
expect(localizations.formatMinute(const TimeOfDay(hour: 1, minute: 32)), '32');
|
||||
});
|
||||
|
||||
test('formats Arabic', () {
|
||||
final GlobalMaterialLocalizations localizations = new GlobalMaterialLocalizations(const Locale('ar', ''));
|
||||
expect(localizations.formatMinute(const TimeOfDay(hour: 1, minute: 32)), '٣٢');
|
||||
});
|
||||
});
|
||||
|
||||
group('formatTimeOfDay', () {
|
||||
test('formats ${TimeOfDayFormat.h_colon_mm_space_a}', () {
|
||||
GlobalMaterialLocalizations localizations;
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('ar', ''));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '٩:٣٢ ص');
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('en', ''));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32 AM');
|
||||
});
|
||||
|
||||
test('formats ${TimeOfDayFormat.HH_colon_mm}', () {
|
||||
GlobalMaterialLocalizations localizations;
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('de', ''));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09:32');
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('en', 'ZA'));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09:32');
|
||||
});
|
||||
|
||||
test('formats ${TimeOfDayFormat.H_colon_mm}', () {
|
||||
GlobalMaterialLocalizations localizations;
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('es', ''));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32');
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('ja', ''));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32');
|
||||
});
|
||||
|
||||
test('formats ${TimeOfDayFormat.frenchCanadian}', () {
|
||||
GlobalMaterialLocalizations localizations;
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('fr', 'CA'));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09 h 32');
|
||||
});
|
||||
|
||||
test('formats ${TimeOfDayFormat.a_space_h_colon_mm}', () {
|
||||
GlobalMaterialLocalizations localizations;
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('zh', ''));
|
||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '上午 9:32');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
214
packages/flutter_localizations/test/override_test.dart
Normal file
214
packages/flutter_localizations/test/override_test.dart
Normal file
@ -0,0 +1,214 @@
|
||||
// Copyright 2016 The Chromium 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:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FooMaterialLocalizations extends GlobalMaterialLocalizations {
|
||||
FooMaterialLocalizations(Locale locale) : super(locale);
|
||||
|
||||
@override
|
||||
String get backButtonTooltip => 'foo';
|
||||
}
|
||||
|
||||
class FooMaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
|
||||
const FooMaterialLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<FooMaterialLocalizations> load(Locale locale) {
|
||||
return new SynchronousFuture<FooMaterialLocalizations>(new FooMaterialLocalizations(locale));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReload(FooMaterialLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
Widget buildFrame({
|
||||
Locale locale,
|
||||
Iterable<LocalizationsDelegate<dynamic>> delegates: GlobalMaterialLocalizations.delegates,
|
||||
WidgetBuilder buildContent,
|
||||
LocaleResolutionCallback localeResolutionCallback,
|
||||
Iterable<Locale> supportedLocales: const <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
const Locale('es', 'es'),
|
||||
],
|
||||
}) {
|
||||
return new MaterialApp(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
locale: locale,
|
||||
supportedLocales: supportedLocales,
|
||||
localizationsDelegates: delegates,
|
||||
localeResolutionCallback: localeResolutionCallback,
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return new MaterialPageRoute<Null>(
|
||||
builder: (BuildContext context) {
|
||||
return buildContent(context);
|
||||
}
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('Locale fallbacks', (WidgetTester tester) async {
|
||||
final Key textKey = new UniqueKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
buildContent: (BuildContext context) {
|
||||
return new Text(
|
||||
MaterialLocalizations.of(context).backButtonTooltip,
|
||||
key: textKey,
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'Back');
|
||||
|
||||
// Unrecognized locale falls back to 'en'
|
||||
await tester.binding.setLocale('foo', 'bar');
|
||||
await tester.pump();
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'Back');
|
||||
|
||||
// Spanish Bolivia locale, falls back to just 'es'
|
||||
await tester.binding.setLocale('es', 'bo');
|
||||
await tester.pump();
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'Espalda');
|
||||
});
|
||||
|
||||
testWidgets('Localizations.override widget tracks parent\'s locale', (WidgetTester tester) async {
|
||||
Widget buildLocaleFrame(Locale locale) {
|
||||
return buildFrame(
|
||||
locale: locale,
|
||||
buildContent: (BuildContext context) {
|
||||
return new Localizations.override(
|
||||
context: context,
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
// No MaterialLocalizations are defined for the first Localizations
|
||||
// ancestor, so we should get the values from the default one, i.e.
|
||||
// the one created by WidgetsApp via the LocalizationsDelegate
|
||||
// provided by MaterialApp.
|
||||
return new Text(MaterialLocalizations.of(context).backButtonTooltip);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US')));
|
||||
expect(find.text('Back'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildLocaleFrame(const Locale('de', 'DE')));
|
||||
expect(find.text('Zurück'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildLocaleFrame(const Locale('zh', 'CN')));
|
||||
expect(find.text('返回'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Localizations.override widget with hardwired locale', (WidgetTester tester) async {
|
||||
Widget buildLocaleFrame(Locale locale) {
|
||||
return buildFrame(
|
||||
locale: locale,
|
||||
buildContent: (BuildContext context) {
|
||||
return new Localizations.override(
|
||||
context: context,
|
||||
locale: const Locale('en', 'US'),
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
// No MaterialLocalizations are defined for the Localizations.override
|
||||
// ancestor, so we should get all values from the default one, i.e.
|
||||
// the one created by WidgetsApp via the LocalizationsDelegate
|
||||
// provided by MaterialApp.
|
||||
return new Text(MaterialLocalizations.of(context).backButtonTooltip);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US')));
|
||||
expect(find.text('Back'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildLocaleFrame(const Locale('de', 'DE')));
|
||||
expect(find.text('Back'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildLocaleFrame(const Locale('zh', 'CN')));
|
||||
expect(find.text('Back'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('MaterialApp overrides MaterialLocalizations', (WidgetTester tester) async {
|
||||
final Key textKey = new UniqueKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
// Accept whatever locale we're given
|
||||
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
|
||||
delegates: <FooMaterialLocalizationsDelegate>[
|
||||
const FooMaterialLocalizationsDelegate(),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
// Should always be 'foo', no matter what the locale is
|
||||
return new Text(
|
||||
MaterialLocalizations.of(context).backButtonTooltip,
|
||||
key: textKey,
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'foo');
|
||||
|
||||
await tester.binding.setLocale('zh', 'CN');
|
||||
await tester.pump();
|
||||
expect(find.text('foo'), findsOneWidget);
|
||||
|
||||
await tester.binding.setLocale('de', 'DE');
|
||||
await tester.pump();
|
||||
expect(find.text('foo'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('deprecated Android/Java locales are modernized', (WidgetTester tester) async {
|
||||
final Key textKey = new UniqueKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
supportedLocales: <Locale>[
|
||||
const Locale('en', 'US'),
|
||||
const Locale('he', 'IL'),
|
||||
const Locale('yi', 'IL'),
|
||||
const Locale('id', 'JV'),
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
return new Text(
|
||||
'${Localizations.localeOf(context)}',
|
||||
key: textKey,
|
||||
);
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'en_US');
|
||||
|
||||
// Hebrew was iw (ISO-639) is he (ISO-639-1)
|
||||
await tester.binding.setLocale('iw', 'IL');
|
||||
await tester.pump();
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'he_IL');
|
||||
|
||||
// Yiddish was ji (ISO-639) is yi (ISO-639-1)
|
||||
await tester.binding.setLocale('ji', 'IL');
|
||||
await tester.pump();
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'yi_IL');
|
||||
|
||||
// Indonesian was in (ISO-639) is id (ISO-639-1)
|
||||
await tester.binding.setLocale('in', 'JV');
|
||||
await tester.pump();
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'id_JV');
|
||||
});
|
||||
}
|
129
packages/flutter_localizations/test/time_picker_test.dart
Normal file
129
packages/flutter_localizations/test/time_picker_test.dart
Normal file
@ -0,0 +1,129 @@
|
||||
// Copyright 2016 The Chromium 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:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class _TimePickerLauncher extends StatelessWidget {
|
||||
const _TimePickerLauncher({ Key key, this.onChanged, this.locale }) : super(key: key);
|
||||
|
||||
final ValueChanged<TimeOfDay> onChanged;
|
||||
final Locale locale;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new MaterialApp(
|
||||
locale: locale,
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
home: new Material(
|
||||
child: new Center(
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return new RaisedButton(
|
||||
child: const Text('X'),
|
||||
onPressed: () async {
|
||||
onChanged(await showTimePicker(
|
||||
context: context,
|
||||
initialTime: const TimeOfDay(hour: 7, minute: 0)
|
||||
));
|
||||
}
|
||||
);
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Offset> startPicker(WidgetTester tester, ValueChanged<TimeOfDay> onChanged,
|
||||
{ Locale locale: const Locale('en', 'US') }) async {
|
||||
await tester.pumpWidget(new _TimePickerLauncher(onChanged: onChanged, locale: locale,));
|
||||
await tester.tap(find.text('X'));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
return tester.getCenter(find.byKey(const Key('time-picker-dial')));
|
||||
}
|
||||
|
||||
Future<Null> finishPicker(WidgetTester tester) async {
|
||||
final Element timePickerElement = tester.element(find.byElementPredicate((Element element) => element.widget.runtimeType.toString() == '_TimePickerDialog'));
|
||||
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(timePickerElement);
|
||||
await tester.tap(find.text(materialLocalizations.okButtonLabel));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('can localize the header in all known formats', (WidgetTester tester) async {
|
||||
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
|
||||
final Map<Locale, List<String>> locales = <Locale, List<String>>{
|
||||
const Locale('en', 'US'): const <String>['hour h', 'string :', 'minute', 'period'], //'h:mm a'
|
||||
const Locale('en', 'GB'): const <String>['hour HH', 'string :', 'minute'], //'HH:mm'
|
||||
const Locale('es', 'ES'): const <String>['hour H', 'string :', 'minute'], //'H:mm'
|
||||
const Locale('fr', 'CA'): const <String>['hour HH', 'string h', 'minute'], //'HH \'h\' mm'
|
||||
const Locale('zh', 'ZH'): const <String>['period', 'hour h', 'string :', 'minute'], //'ah:mm'
|
||||
};
|
||||
|
||||
for (Locale locale in locales.keys) {
|
||||
final Offset center = await startPicker(tester, (TimeOfDay time) { }, locale: locale);
|
||||
final List<String> actual = <String>[];
|
||||
tester.element(find.byType(CustomMultiChildLayout)).visitChildren((Element child) {
|
||||
final LayoutId layout = child.widget;
|
||||
final String fragmentType = '${layout.child.runtimeType}';
|
||||
final dynamic widget = layout.child;
|
||||
if (fragmentType == '_MinuteControl') {
|
||||
actual.add('minute');
|
||||
} else if (fragmentType == '_DayPeriodControl') {
|
||||
actual.add('period');
|
||||
} else if (fragmentType == '_HourControl') {
|
||||
actual.add('hour ${widget.hourFormat.toString().split('.').last}');
|
||||
} else if (fragmentType == '_StringFragment') {
|
||||
actual.add('string ${widget.value}');
|
||||
} else {
|
||||
fail('Unsupported fragment type: $fragmentType');
|
||||
}
|
||||
});
|
||||
expect(actual, locales[locale]);
|
||||
await tester.tapAt(new Offset(center.dx, center.dy - 50.0));
|
||||
await finishPicker(tester);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('uses single-ring 12-hour dial for h hour format', (WidgetTester tester) async {
|
||||
// Tap along the segment stretching from the center to the edge at
|
||||
// 12:00 AM position. Because there's only one ring, no matter where you
|
||||
// tap the time will be the same. See the 24-hour dial test that behaves
|
||||
// differently.
|
||||
for (int i = 1; i < 10; i++) {
|
||||
TimeOfDay result;
|
||||
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
||||
final Size size = tester.getSize(find.byKey(const Key('time-picker-dial')));
|
||||
final double dy = (size.height / 2.0 / 10) * i;
|
||||
await tester.tapAt(new Offset(center.dx, center.dy - dy));
|
||||
await finishPicker(tester);
|
||||
expect(result, equals(const TimeOfDay(hour: 0, minute: 0)));
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('uses two-ring 24-hour dial for H and HH hour formats', (WidgetTester tester) async {
|
||||
const List<Locale> locales = const <Locale>[
|
||||
const Locale('en', 'GB'), // HH
|
||||
const Locale('es', 'ES'), // H
|
||||
];
|
||||
for (Locale locale in locales) {
|
||||
// Tap along the segment stretching from the center to the edge at
|
||||
// 12:00 AM position. There are two rings. At ~70% mark, the ring
|
||||
// switches between inner ring and outer ring.
|
||||
for (int i = 1; i < 10; i++) {
|
||||
TimeOfDay result;
|
||||
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; }, locale: locale);
|
||||
final Size size = tester.getSize(find.byKey(const Key('time-picker-dial')));
|
||||
final double dy = (size.height / 2.0 / 10) * i;
|
||||
await tester.tapAt(new Offset(center.dx, center.dy - dy));
|
||||
await finishPicker(tester);
|
||||
expect(result, equals(new TimeOfDay(hour: i < 7 ? 12 : 0, minute: 0)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
89
packages/flutter_localizations/test/translations_test.dart
Normal file
89
packages/flutter_localizations/test/translations_test.dart
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2017 The Chromium 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/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
final List<String> languages = <String>[
|
||||
'ar', // Arabic
|
||||
'de', // German
|
||||
'en', // English
|
||||
'es', // Spanish
|
||||
'fa', // Farsi (Persian)
|
||||
'fr', // French
|
||||
'he', // Hebrew
|
||||
'it', // Italian
|
||||
'ja', // Japanese
|
||||
'ps', // Pashto
|
||||
'pt', // Portugese
|
||||
'ru', // Russian
|
||||
'sd', // Sindhi
|
||||
'ur', // Urdu
|
||||
'zh', // Chinese (simplified)
|
||||
];
|
||||
|
||||
for (String language in languages) {
|
||||
testWidgets('translations exist for $language', (WidgetTester tester) async {
|
||||
final Locale locale = new Locale(language, '');
|
||||
final MaterialLocalizations localizations = new GlobalMaterialLocalizations(locale);
|
||||
|
||||
expect(localizations.openAppDrawerTooltip, isNotNull);
|
||||
expect(localizations.backButtonTooltip, isNotNull);
|
||||
expect(localizations.closeButtonTooltip, isNotNull);
|
||||
expect(localizations.nextMonthTooltip, isNotNull);
|
||||
expect(localizations.previousMonthTooltip, isNotNull);
|
||||
expect(localizations.nextPageTooltip, isNotNull);
|
||||
expect(localizations.previousPageTooltip, isNotNull);
|
||||
expect(localizations.showMenuTooltip, isNotNull);
|
||||
expect(localizations.licensesPageTitle, isNotNull);
|
||||
expect(localizations.rowsPerPageTitle, isNotNull);
|
||||
expect(localizations.cancelButtonLabel, isNotNull);
|
||||
expect(localizations.closeButtonLabel, isNotNull);
|
||||
expect(localizations.continueButtonLabel, isNotNull);
|
||||
expect(localizations.copyButtonLabel, isNotNull);
|
||||
expect(localizations.cutButtonLabel, isNotNull);
|
||||
expect(localizations.okButtonLabel, isNotNull);
|
||||
expect(localizations.pasteButtonLabel, isNotNull);
|
||||
expect(localizations.selectAllButtonLabel, isNotNull);
|
||||
expect(localizations.viewLicensesButtonLabel, isNotNull);
|
||||
|
||||
expect(localizations.aboutListTileTitle('FOO'), isNotNull);
|
||||
expect(localizations.aboutListTileTitle('FOO'), contains('FOO'));
|
||||
|
||||
expect(localizations.selectedRowCountTitle(0), isNotNull);
|
||||
expect(localizations.selectedRowCountTitle(1), isNotNull);
|
||||
expect(localizations.selectedRowCountTitle(2), isNotNull);
|
||||
expect(localizations.selectedRowCountTitle(100), isNotNull);
|
||||
expect(localizations.selectedRowCountTitle(0).contains(r'$selectedRowCount'), isFalse);
|
||||
expect(localizations.selectedRowCountTitle(1).contains(r'$selectedRowCount'), isFalse);
|
||||
expect(localizations.selectedRowCountTitle(2).contains(r'$selectedRowCount'), isFalse);
|
||||
expect(localizations.selectedRowCountTitle(100).contains(r'$selectedRowCount'), isFalse);
|
||||
|
||||
expect(localizations.pageRowsInfoTitle(1, 10, 100, true), isNotNull);
|
||||
expect(localizations.pageRowsInfoTitle(1, 10, 100, false), isNotNull);
|
||||
expect(localizations.pageRowsInfoTitle(1, 10, 100, true).contains(r'$firstRow'), isFalse);
|
||||
expect(localizations.pageRowsInfoTitle(1, 10, 100, true).contains(r'$lastRow'), isFalse);
|
||||
expect(localizations.pageRowsInfoTitle(1, 10, 100, true).contains(r'$rowCount'), isFalse);
|
||||
expect(localizations.pageRowsInfoTitle(1, 10, 100, false).contains(r'$firstRow'), isFalse);
|
||||
expect(localizations.pageRowsInfoTitle(1, 10, 100, false).contains(r'$lastRow'), isFalse);
|
||||
expect(localizations.pageRowsInfoTitle(1, 10, 100, false).contains(r'$rowCount'), isFalse);
|
||||
});
|
||||
}
|
||||
|
||||
testWidgets('spot check selectedRowCount translations', (WidgetTester tester) async {
|
||||
MaterialLocalizations localizations = new GlobalMaterialLocalizations(const Locale('en', ''));
|
||||
expect(localizations.selectedRowCountTitle(0), 'No items selected');
|
||||
expect(localizations.selectedRowCountTitle(1), '1 item selected');
|
||||
expect(localizations.selectedRowCountTitle(2), '2 items selected');
|
||||
expect(localizations.selectedRowCountTitle(123456789), '123,456,789 items selected');
|
||||
|
||||
localizations = new GlobalMaterialLocalizations(const Locale('es', ''));
|
||||
expect(localizations.selectedRowCountTitle(0), 'No se han seleccionado elementos');
|
||||
expect(localizations.selectedRowCountTitle(1), '1 artículo seleccionado');
|
||||
expect(localizations.selectedRowCountTitle(2), '2 artículos seleccionados');
|
||||
expect(localizations.selectedRowCountTitle(123456789), '123.456.789 artículos seleccionados');
|
||||
});
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
class TestLocalizations {
|
||||
TestLocalizations(this.locale, this.prefix);
|
||||
@ -103,20 +104,7 @@ class AsyncMoreLocalizationsDelegate extends LocalizationsDelegate<MoreLocalizat
|
||||
bool shouldReload(AsyncMoreLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
// Same as _WidgetsLocalizationsDelegate in widgets/app.dart
|
||||
class DefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
|
||||
const DefaultWidgetsLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
|
||||
|
||||
@override
|
||||
bool shouldReload(DefaultWidgetsLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
class OnlyRTLDefaultWidgetsLocalizations extends DefaultWidgetsLocalizations {
|
||||
OnlyRTLDefaultWidgetsLocalizations(Locale locale) : super(locale);
|
||||
|
||||
@override
|
||||
TextDirection get textDirection => TextDirection.rtl;
|
||||
}
|
||||
@ -126,7 +114,7 @@ class OnlyRTLDefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<W
|
||||
|
||||
@override
|
||||
Future<WidgetsLocalizations> load(Locale locale) {
|
||||
return new SynchronousFuture<WidgetsLocalizations>(new OnlyRTLDefaultWidgetsLocalizations(locale));
|
||||
return new SynchronousFuture<WidgetsLocalizations>(new OnlyRTLDefaultWidgetsLocalizations());
|
||||
}
|
||||
|
||||
@override
|
||||
@ -224,7 +212,7 @@ void main() {
|
||||
testWidgets('Synchronously loaded localizations in a WidgetsApp', (WidgetTester tester) async {
|
||||
final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[
|
||||
new SyncTestLocalizationsDelegate(),
|
||||
const DefaultWidgetsLocalizationsDelegate(),
|
||||
DefaultWidgetsLocalizations.delegate,
|
||||
];
|
||||
|
||||
Future<Null> pumpTest(Locale locale) async {
|
||||
@ -349,7 +337,7 @@ void main() {
|
||||
locale: const Locale('en', 'GB'),
|
||||
delegates: <LocalizationsDelegate<dynamic>>[
|
||||
new SyncTestLocalizationsDelegate(),
|
||||
const DefaultWidgetsLocalizationsDelegate(),
|
||||
DefaultWidgetsLocalizations.delegate,
|
||||
],
|
||||
// Create a new context within the en_GB Localization
|
||||
child: new Builder(
|
||||
@ -476,6 +464,9 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
delegates: const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const <Locale>[
|
||||
const Locale('en', 'GB'),
|
||||
const Locale('ar', 'EG'),
|
||||
@ -557,6 +548,9 @@ void main() {
|
||||
buildFrame(
|
||||
// Accept whatever locale we're given
|
||||
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
|
||||
delegates: const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
return new Localizations.override(
|
||||
context: context,
|
@ -27,7 +27,7 @@ dependencies:
|
||||
|
||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||
|
@ -48,7 +48,7 @@ dev_dependencies:
|
||||
mockito: 2.2.0
|
||||
|
||||
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+12 # TRANSITIVE DEPENDENCY
|
||||
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||
boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
|
||||
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||
convert: 2.0.1 # TRANSITIVE DEPENDENCY
|
||||
|
Loading…
x
Reference in New Issue
Block a user