flutter_localization optional package (#12410)

This commit is contained in:
Hans Muller 2017-10-11 16:01:13 -07:00 committed by GitHub
parent f07170b45b
commit c3d56b1dad
68 changed files with 1762 additions and 1202 deletions

View File

@ -7,7 +7,7 @@ dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -19,7 +19,7 @@ dev_dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -11,7 +11,7 @@ dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -6,7 +6,7 @@ dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -57,13 +57,13 @@ Future<Null> _verifyInternationalizations() async {
dart, dart,
<String>[ <String>[
path.join('dev', 'tools', 'gen_localizations.dart'), path.join('dev', 'tools', 'gen_localizations.dart'),
path.join('packages', 'flutter', 'lib', 'src', 'material', 'i18n'), path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n'),
'material' 'material'
], ],
workingDirectory: flutterRoot, 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( final EvalResult sourceContents = await _evalCommand(
'cat', 'cat',
@ -156,6 +156,7 @@ Future<Null> _runTests() async {
// Run tests. // Run tests.
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter')); 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_driver'));
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test')); await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'));
await _pubRunTest(path.join(flutterRoot, 'packages', 'flutter_tools')); await _pubRunTest(path.join(flutterRoot, 'packages', 'flutter_tools'));

View File

@ -24,7 +24,7 @@ dev_dependencies:
archive: 1.0.31 # TRANSITIVE DEPENDENCY archive: 1.0.31 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
browser: 0.10.0+2 # TRANSITIVE DEPENDENCY browser: 0.10.0+2 # TRANSITIVE DEPENDENCY
charcode: 1.1.1 # TRANSITIVE DEPENDENCY charcode: 1.1.1 # TRANSITIVE DEPENDENCY

View File

@ -9,7 +9,7 @@ dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -9,7 +9,7 @@ dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -9,7 +9,7 @@ dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -14,7 +14,7 @@ dev_dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
charcode: 1.1.1 # TRANSITIVE DEPENDENCY charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY

View File

@ -10,7 +10,7 @@ dev_dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -10,7 +10,7 @@ dependencies:
path: 1.4.2 path: 1.4.2
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -13,7 +13,7 @@ dev_dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -21,7 +21,7 @@ dev_dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
convert: 2.0.1 # TRANSITIVE DEPENDENCY convert: 2.0.1 # TRANSITIVE DEPENDENCY

View File

@ -7,7 +7,7 @@ dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -10,7 +10,7 @@ dev_dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -9,7 +9,7 @@ dev_dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -12,7 +12,7 @@ dev_dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -12,7 +12,7 @@ dev_dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -6,7 +6,7 @@ dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -13,6 +13,7 @@ import 'package:flutter/rendering.dart' show
debugPaintLayerBordersEnabled, debugPaintLayerBordersEnabled,
debugPaintPointersEnabled, debugPaintPointersEnabled,
debugRepaintRainbowEnabled; debugRepaintRainbowEnabled;
import 'package:flutter_localizations/flutter_localizations.dart';
import 'stock_data.dart'; import 'stock_data.dart';
import 'stock_home.dart'; import 'stock_home.dart';
@ -118,8 +119,10 @@ class StocksAppState extends State<StocksApp> {
return new MaterialApp( return new MaterialApp(
title: 'Stocks', title: 'Stocks',
theme: theme, theme: theme,
localizationsDelegates: <_StocksLocalizationsDelegate>[ localizationsDelegates: <LocalizationsDelegate<dynamic>>[
new _StocksLocalizationsDelegate(), new _StocksLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
], ],
supportedLocales: const <Locale>[ supportedLocales: const <Locale>[
const Locale('en', 'US'), const Locale('en', 'US'),

View File

@ -2,6 +2,8 @@ name: stocks
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_localizations:
sdk: flutter
intl: 0.15.1 intl: 0.15.1
intl_translation: 0.15.0 intl_translation: 0.15.0
http: 0.11.3+14 http: 0.11.3+14
@ -15,7 +17,7 @@ dev_dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
collection: 1.14.3 # TRANSITIVE DEPENDENCY collection: 1.14.3 # TRANSITIVE DEPENDENCY

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -26,17 +24,6 @@ const TextStyle _errorTextStyle = const TextStyle(
decorationStyle: TextDecorationStyle.double 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. /// An application that uses material design.
/// ///
/// A convenience widget that wraps a number of widgets that are commonly /// 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* { Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
if (widget.localizationsDelegates != null) if (widget.localizationsDelegates != null)
yield* widget.localizationsDelegates; yield* widget.localizationsDelegates;
yield const _MaterialLocalizationsDelegate(); yield DefaultMaterialLocalizations.delegate;
} }
RectTween _createRectTween(Rect begin, Rect end) { RectTween _createRectTween(Rect begin, Rect end) {

View File

@ -295,8 +295,8 @@ class DayPicker extends StatelessWidget {
List<Widget> _getDayHeaders(TextStyle headerStyle, MaterialLocalizations localizations) { List<Widget> _getDayHeaders(TextStyle headerStyle, MaterialLocalizations localizations) {
final List<Widget> result = <Widget>[]; final List<Widget> result = <Widget>[];
for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) { for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) {
final String weekDay = localizations.narrowWeekDays[i]; final String weekday = localizations.narrowWeekdays[i];
result.add(new Center(child: new Text(weekDay, style: headerStyle))); result.add(new Center(child: new Text(weekday, style: headerStyle)));
if (i == (localizations.firstDayOfWeekIndex - 1) % 7) if (i == (localizations.firstDayOfWeekIndex - 1) % 7)
break; break;
} }
@ -350,19 +350,19 @@ class DayPicker extends StatelessWidget {
/// - [DateTime.weekday] provides a 1-based index into days of week, with 1 /// - [DateTime.weekday] provides a 1-based index into days of week, with 1
/// falling on Monday. /// falling on Monday.
/// - [MaterialLocalizations.firstDayOfWeekIndex] provides a 0-based index /// - [MaterialLocalizations.firstDayOfWeekIndex] provides a 0-based index
/// into the [MaterialLocalizations.narrowWeekDays] list. /// into the [MaterialLocalizations.narrowWeekdays] list.
/// - [MaterialLocalizations.narrowWeekDays] list provides localized names of /// - [MaterialLocalizations.narrowWeekdays] list provides localized names of
/// days of week, always starting with Sunday and ending with Saturday. /// days of week, always starting with Sunday and ending with Saturday.
int _computeFirstDayOffset(int year, int month, MaterialLocalizations localizations) { int _computeFirstDayOffset(int year, int month, MaterialLocalizations localizations) {
// 0-based day of week, with 0 representing Monday. // 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. // 0-based day of week, with 0 representing Sunday.
final int firstDayOfWeekFromSunday = localizations.firstDayOfWeekIndex; final int firstDayOfWeekFromSunday = localizations.firstDayOfWeekIndex;
// firstDayOfWeekFromSunday recomputed to be Monday-based // firstDayOfWeekFromSunday recomputed to be Monday-based
final int firstDayOfWeekFromMonday = (firstDayOfWeekFromSunday - 1) % 7; final int firstDayOfWeekFromMonday = (firstDayOfWeekFromSunday - 1) % 7;
// Number of days between the first day of week appearing on the calendar, // Number of days between the first day of week appearing on the calendar,
// and the day corresponding to the 1-st of the month. // and the day corresponding to the 1-st of the month.
return (weekDayFromMonday - firstDayOfWeekFromMonday) % 7; return (weekdayFromMonday - firstDayOfWeekFromMonday) % 7;
} }
@override @override

View File

@ -6,11 +6,7 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.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 'time.dart';
import 'typography.dart'; import 'typography.dart';
@ -18,8 +14,10 @@ import 'typography.dart';
/// ///
/// See also: /// See also:
/// ///
/// * [DefaultMaterialLocalizations], which implements this interface /// * [DefaultMaterialLocalizations], the default, English-only, implementation
/// and supports a variety of locales. /// of this interface.
/// * [GlobalMaterialLocalizations], which provides material localizations for
/// many languages.
abstract class MaterialLocalizations { abstract class MaterialLocalizations {
/// The tooltip for the leading [AppBar] menu (aka 'hamburger') button. /// The tooltip for the leading [AppBar] menu (aka 'hamburger') button.
String get openAppDrawerTooltip; String get openAppDrawerTooltip;
@ -155,17 +153,17 @@ abstract class MaterialLocalizations {
/// - US English: S, M, T, W, T, F, S /// - US English: S, M, T, W, T, F, S
/// - Russian: вс, пн, вт, ср, чт, пт, сб - notice that the list begins with /// - Russian: вс, пн, вт, ср, чт, пт, сб - notice that the list begins with
/// вс (Sunday) even though the first day of week for Russian is Monday. /// вс (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 /// Index of the first day of week, where 0 points to Sunday, and 6 points to
/// Saturday. /// Saturday.
/// ///
/// This getter is compatible with [narrowWeekDays]. For example: /// This getter is compatible with [narrowWeekdays]. For example:
/// ///
/// ```dart /// ```dart
/// var localizations = MaterialLocalizations.of(context); /// var localizations = MaterialLocalizations.of(context);
/// // The name of the first day of week for the current locale. /// // 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; 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 { class DefaultMaterialLocalizations implements MaterialLocalizations {
/// Constructs an object that defines the material widgets' localized strings /// 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] /// [LocalizationsDelegate] implementations typically call the static [load]
/// function, rather than constructing this class directly. /// function, rather than constructing this class directly.
DefaultMaterialLocalizations(this.locale) const DefaultMaterialLocalizations();
: 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'; // Ordered to match DateTime.MONDAY=1, DateTime.SUNDAY=6
if (intl.DateFormat.localeExists(_localeName)) { static const List<String>_shortWeekdays = const <String>[
_fullYearFormat = new intl.DateFormat.y(_localeName); 'Mon',
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern, _localeName); 'Tue',
_yearMonthFormat = new intl.DateFormat('yMMMM', _localeName); 'Wed',
} else if (intl.DateFormat.localeExists(locale.languageCode)) { 'Thu',
_fullYearFormat = new intl.DateFormat.y(locale.languageCode); 'Fri',
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern, locale.languageCode); 'Sat',
_yearMonthFormat = new intl.DateFormat('yMMMM', locale.languageCode); 'Sun',
} else { ];
_fullYearFormat = new intl.DateFormat.y();
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern);
_yearMonthFormat = new intl.DateFormat('yMMMM');
}
if (intl.NumberFormat.localeExists(_localeName)) { static const List<String> _narrowWeekdays = const <String>[
_decimalFormat = new intl.NumberFormat.decimalPattern(_localeName); 'S',
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00', _localeName); 'M',
} else if (intl.NumberFormat.localeExists(locale.languageCode)) { 'T',
_decimalFormat = new intl.NumberFormat.decimalPattern(locale.languageCode); 'W',
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00', locale.languageCode); 'T',
} else { 'F',
_decimalFormat = new intl.NumberFormat.decimalPattern(); 'S',
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00'); ];
}
}
/// The locale for which the values of this class's localized resources static const List<String> _shortMonths = const <String>[
/// have been translated. 'Jan',
final Locale locale; 'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
final String _localeName; static const List<String> _months = const <String>[
'January',
final Map<String, String> _nameToValue = <String, String>{}; 'February',
'March',
/// Formats numbers using variable length format with no zero padding. 'April',
/// 'May',
/// See also [_twoDigitZeroPaddedFormat]. 'June',
intl.NumberFormat _decimalFormat; 'July',
'August',
/// Formats numbers as two-digits. 'September',
/// 'October',
/// If the number is less than 10, zero-pads it. 'November',
intl.NumberFormat _twoDigitZeroPaddedFormat; 'December',
];
/// 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;
}
@override @override
String formatHour(TimeOfDay timeOfDay) { String formatHour(TimeOfDay timeOfDay) {
switch (hourFormat(of: timeOfDayFormat)) { assert(hourFormat(of: timeOfDayFormat) == HourFormat.h);
case HourFormat.HH: return formatDecimal(timeOfDay.hour);
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 @override
String formatMinute(TimeOfDay timeOfDay) { String formatMinute(TimeOfDay timeOfDay) {
return _twoDigitZeroPaddedFormat.format(timeOfDay.minute); final int minute = timeOfDay.minute;
return minute < 10 ? '0$minute' : minute.toString();
} }
@override @override
String formatYear(DateTime date) { String formatYear(DateTime date) => date.year.toString();
return _fullYearFormat.format(date);
}
@override @override
String formatMediumDate(DateTime date) { 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 @override
String formatMonthYear(DateTime date) { 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 @override
List<String> get narrowWeekDays { List<String> get narrowWeekdays => _narrowWeekdays;
return _fullYearFormat.dateSymbols.NARROWWEEKDAYS;
}
@override @override
int get firstDayOfWeekIndex => (_fullYearFormat.dateSymbols.FIRSTDAYOFWEEK + 1) % 7; int get firstDayOfWeekIndex => 0; // narrowWeekdays[0] is 'S' for Sunday
/// 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) { String _formatDayPeriod(TimeOfDay timeOfDay) {
switch (timeOfDay.period) { switch (timeOfDay.period) {
@ -364,166 +307,142 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
return null; return null;
} }
@override /// Formats an integer, inserting thousands separators as needed.
String get openAppDrawerTooltip => _nameToValue['openAppDrawerTooltip']; String formatDecimal(int number) {
if (number > -1000 && number < 1000)
return number.toString();
@override final String digits = number.abs().toString();
String get backButtonTooltip => _nameToValue['backButtonTooltip']; final StringBuffer result = new StringBuffer(number < 0 ? '-' : '');
final int maxDigitIndex = digits.length - 1;
@override for (int i = 0; i <= maxDigitIndex; i += 1) {
String get closeButtonTooltip => _nameToValue['closeButtonTooltip']; result.write(digits[i]);
if (i < maxDigitIndex && (maxDigitIndex - i) % 3 == 0)
@override result.write(',');
String get nextMonthTooltip => _nameToValue['nextMonthTooltip']; }
return result.toString();
@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 @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 @override
String pageRowsInfoTitle(int firstRow, int lastRow, int rowCount, bool rowCountIsApproximate) { String pageRowsInfoTitle(int firstRow, int lastRow, int rowCount, bool rowCountIsApproximate) {
String text = rowCountIsApproximate ? _nameToValue['pageRowsInfoTitleApproximate'] : null; return rowCountIsApproximate
text ??= _nameToValue['pageRowsInfoTitle']; ? '$firstRow$lastRow of about $rowCount'
assert(text != null, 'A $locale localization was not found for pageRowsInfoTitle or pageRowsInfoTitleApproximate'); : '$firstRow$lastRow of $rowCount';
// 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 @override
String get rowsPerPageTitle => _nameToValue['rowsPerPageTitle']; String get rowsPerPageTitle => 'Rows per page';
@override @override
String selectedRowCountTitle(int selectedRowCount) { String selectedRowCountTitle(int selectedRowCount) {
return _nameToPluralValue(selectedRowCount, 'selectedRowCountTitle') // asserts on no match switch (selectedRowCount) {
.replaceFirst(r'$selectedRowCount', formatDecimal(selectedRowCount)); case 0:
return 'No items selected';
case 1:
return '1 item selected';
default:
return '$selectedRowCount items selected';
}
} }
@override @override
String get cancelButtonLabel => _nameToValue['cancelButtonLabel']; String get cancelButtonLabel => 'CANCEL';
@override @override
String get closeButtonLabel => _nameToValue['closeButtonLabel']; String get closeButtonLabel => 'CLOSE';
@override @override
String get continueButtonLabel => _nameToValue['continueButtonLabel']; String get continueButtonLabel => 'CONTINUE';
@override @override
String get copyButtonLabel => _nameToValue['copyButtonLabel']; String get copyButtonLabel => 'COPY';
@override @override
String get cutButtonLabel => _nameToValue['cutButtonLabel']; String get cutButtonLabel => 'CUT';
@override @override
String get okButtonLabel => _nameToValue['okButtonLabel']; String get okButtonLabel => 'OK';
@override @override
String get pasteButtonLabel => _nameToValue['pasteButtonLabel']; String get pasteButtonLabel => 'PASTE';
@override @override
String get selectAllButtonLabel => _nameToValue['selectAllButtonLabel']; String get selectAllButtonLabel => 'SELECT ALL';
@override @override
String get viewLicensesButtonLabel => _nameToValue['viewLicensesButtonLabel']; String get viewLicensesButtonLabel => 'VIEW LICENSES';
@override @override
String get anteMeridiemAbbreviation => _nameToValue['anteMeridiemAbbreviation']; String get anteMeridiemAbbreviation => 'AM';
@override @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 @override
TimeOfDayFormat get timeOfDayFormat { TimeOfDayFormat get timeOfDayFormat => TimeOfDayFormat.h_colon_mm_space_a;
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]. /// Looks up text geometry defined in [MaterialTextGeometry].
@override @override
TextTheme get localTextGeometry => MaterialTextGeometry.forScriptCategory(_nameToValue["scriptCategory"]); TextTheme get localTextGeometry => MaterialTextGeometry.englishLike;
/// Creates an object that provides localized resource values for the /// Creates an object that provides US English resource values for the material
/// for the widgets of the material library. /// library widgets.
///
/// The [locale] parameter is ignored.
/// ///
/// This method is typically used to create a [LocalizationsDelegate]. /// This method is typically used to create a [LocalizationsDelegate].
/// The [MaterialApp] does so by default. /// The [MaterialApp] does so by default.
static Future<MaterialLocalizations> load(Locale locale) { static Future<MaterialLocalizations> load(Locale locale) {
return new SynchronousFuture<MaterialLocalizations>(new DefaultMaterialLocalizations(locale)); return new SynchronousFuture<MaterialLocalizations>(const DefaultMaterialLocalizations());
}
}
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;
} }
/// 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();
} }

View File

@ -44,17 +44,6 @@ typedef Locale LocaleResolutionCallback(Locale locale, Iterable<Locale> supporte
/// This function must not return null. /// This function must not return null.
typedef String GenerateAppTitle(BuildContext context); 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 /// A convenience class that wraps a number of widgets that are commonly
/// required for an application. /// 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 // by the localizationsDelegates parameter, if any. Only the first delegate
// of a particular LocalizationsDelegate.type is loaded so the // of a particular LocalizationsDelegate.type is loaded so the
// localizationsDelegate parameter can be used to override // localizationsDelegate parameter can be used to override
// _WidgetsLocalizationsDelegate. // WidgetsLocalizations.delegate.
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* { Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
if (widget.localizationsDelegates != null) if (widget.localizationsDelegates != null)
yield* widget.localizationsDelegates; yield* widget.localizationsDelegates;
yield const _WidgetsLocalizationsDelegate(); yield DefaultWidgetsLocalizations.delegate;
} }
@override @override

View File

@ -161,44 +161,50 @@ abstract class WidgetsLocalizations {
} }
} }
/// Localized values for widgets. class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
class DefaultWidgetsLocalizations implements WidgetsLocalizations { const _WidgetsLocalizationsDelegate();
/// 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;
@override @override
TextDirection get textDirection => _textDirection; Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
TextDirection _textDirection;
/// Creates an object that provides localized resource values for the @override
/// lowest levels of the Flutter framework. 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]. /// This method is typically used to create a [LocalizationsDelegate].
/// The [WidgetsApp] does so by default. /// The [WidgetsApp] does so by default.
static Future<WidgetsLocalizations> load(Locale locale) { 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 { class _LocalizationsScope extends InheritedWidget {

View File

@ -8,7 +8,6 @@ dependencies:
# To update these, use "flutter update-packages --force-upgrade". # To update these, use "flutter update-packages --force-upgrade".
collection: 1.14.3 collection: 1.14.3
http: 0.11.3+14 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 meta: 1.1.1
typed_data: 1.1.4 typed_data: 1.1.4
vector_math: 2.0.5 vector_math: 2.0.5
@ -23,7 +22,7 @@ dev_dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
charcode: 1.1.1 # TRANSITIVE DEPENDENCY charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
@ -35,6 +34,7 @@ dev_dependencies:
html: 0.13.2 # TRANSITIVE DEPENDENCY html: 0.13.2 # TRANSITIVE DEPENDENCY
http_multi_server: 2.0.4 # TRANSITIVE DEPENDENCY http_multi_server: 2.0.4 # TRANSITIVE DEPENDENCY
http_parser: 3.1.1 # TRANSITIVE DEPENDENCY http_parser: 3.1.1 # TRANSITIVE DEPENDENCY
intl: 0.15.1 # TRANSITIVE DEPENDENCY
intl_translation: 0.15.0 # TRANSITIVE DEPENDENCY intl_translation: 0.15.0 # TRANSITIVE DEPENDENCY
isolate: 1.1.0 # TRANSITIVE DEPENDENCY isolate: 1.1.0 # TRANSITIVE DEPENDENCY
js: 0.6.1 # TRANSITIVE DEPENDENCY js: 0.6.1 # TRANSITIVE DEPENDENCY

View File

@ -4,8 +4,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'feedback_tester.dart'; import 'feedback_tester.dart';
@ -419,250 +417,4 @@ void main() {
expect(await date, isNull); 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;
} }

View File

@ -3,323 +3,51 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.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() { void main() {
testWidgets('sanity check', (WidgetTester tester) async { testWidgets('English translations exist for all MaterialLocalizations properties', (WidgetTester tester) async {
final Key textKey = new UniqueKey(); final MaterialLocalizations localizations = const DefaultMaterialLocalizations();
await tester.pumpWidget( expect(localizations.openAppDrawerTooltip, isNotNull);
buildFrame( expect(localizations.backButtonTooltip, isNotNull);
buildContent: (BuildContext context) { expect(localizations.closeButtonTooltip, isNotNull);
return new Text( expect(localizations.nextMonthTooltip, isNotNull);
MaterialLocalizations.of(context).backButtonTooltip, expect(localizations.previousMonthTooltip, isNotNull);
key: textKey, 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(tester.widget<Text>(find.byKey(textKey)).data, 'Back'); expect(localizations.aboutListTileTitle('FOO'), isNotNull);
expect(localizations.aboutListTileTitle('FOO'), contains('FOO'));
// Unrecognized locale falls back to 'en' expect(localizations.selectedRowCountTitle(0), isNotNull);
await tester.binding.setLocale('foo', 'bar'); expect(localizations.selectedRowCountTitle(1), isNotNull);
await tester.pump(); expect(localizations.selectedRowCountTitle(2), isNotNull);
expect(tester.widget<Text>(find.byKey(textKey)).data, 'Back'); 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);
// Spanish Bolivia locale, falls back to just 'es' expect(localizations.pageRowsInfoTitle(1, 10, 100, true), isNotNull);
await tester.binding.setLocale('es', 'bo'); expect(localizations.pageRowsInfoTitle(1, 10, 100, false), isNotNull);
await tester.pump(); expect(localizations.pageRowsInfoTitle(1, 10, 100, true).contains(r'$firstRow'), isFalse);
expect(tester.widget<Text>(find.byKey(textKey)).data, 'Espalda'); 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);
testWidgets('translations exist for all materia/i18n languages', (WidgetTester tester) async { expect(localizations.pageRowsInfoTitle(1, 10, 100, false).contains(r'$rowCount'), isFalse);
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);
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 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());
}); });
} }

View File

@ -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;
}

View File

@ -205,78 +205,4 @@ void main() {
expect(feedback.hapticCount, 3); 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)));
}
}
});
});
} }

View File

@ -27,7 +27,7 @@ dev_dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
charcode: 1.1.1 # TRANSITIVE DEPENDENCY charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY

View File

@ -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;

View File

@ -1,12 +1,12 @@
# Material Library Internationalization # Material Library Localizations
The `.arb` files in this directory contain localized values (primarily The `.arb` files in this directory contain localized values (primarily
strings) used by the material library. The `localizations.dart` file strings) used by the material library. The `localizations.dart` file
combines all of the localizations into a single Dart Map that is combines all of the localizations into a single Map that is
linked with the rest of the material library. linked with the rest of flutter_localizations package.
If you're looking for information about internationalizing Flutter 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. [Internationalizing Flutter Apps](https://flutter.io/tutorials/internationalization/) tutorial.
@ -54,7 +54,7 @@ translation of "CANCEL" which is defined for the `cancelButtonLabel`
resource ID. resource ID.
Each of the language-specific .arb files contains an entry for 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 generated `localizations.dart` file. The Map is used by the
MaterialLocalizations class. 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 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 see that it was manually generated using a `dev/tools` app called
`gen_localizations` roughly like this: `gen_localizations` roughly like this:
```dart ```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 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 file's locale. The `MaterialLocalizations` class implementation uses
this Map to implement the methods that lookup localized resource this Map to implement the methods that lookup localized resource
values. values.

View File

@ -4,7 +4,7 @@
// This file has been automatically generated. Please do not edit it manually. // This file has been automatically generated. Please do not edit it manually.
// To regenerate the file, use: // 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 /// Maps from [Locale.languageCode] to a map that contains the localized strings
/// for that locale. /// for that locale.
@ -452,4 +452,3 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"postMeridiemAbbreviation": r"下午", "postMeridiemAbbreviation": r"下午",
}, },
}; };

View File

@ -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;
}

View File

@ -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;
}

View 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

View 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();
}
}

View 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,
),
));
}

View 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');
});
});
});
}

View 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');
});
}

View 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)));
}
}
});
}

View 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');
});
}

View File

@ -5,6 +5,7 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
class TestLocalizations { class TestLocalizations {
TestLocalizations(this.locale, this.prefix); TestLocalizations(this.locale, this.prefix);
@ -103,20 +104,7 @@ class AsyncMoreLocalizationsDelegate extends LocalizationsDelegate<MoreLocalizat
bool shouldReload(AsyncMoreLocalizationsDelegate old) => false; 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 { class OnlyRTLDefaultWidgetsLocalizations extends DefaultWidgetsLocalizations {
OnlyRTLDefaultWidgetsLocalizations(Locale locale) : super(locale);
@override @override
TextDirection get textDirection => TextDirection.rtl; TextDirection get textDirection => TextDirection.rtl;
} }
@ -126,7 +114,7 @@ class OnlyRTLDefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<W
@override @override
Future<WidgetsLocalizations> load(Locale locale) { Future<WidgetsLocalizations> load(Locale locale) {
return new SynchronousFuture<WidgetsLocalizations>(new OnlyRTLDefaultWidgetsLocalizations(locale)); return new SynchronousFuture<WidgetsLocalizations>(new OnlyRTLDefaultWidgetsLocalizations());
} }
@override @override
@ -224,7 +212,7 @@ void main() {
testWidgets('Synchronously loaded localizations in a WidgetsApp', (WidgetTester tester) async { testWidgets('Synchronously loaded localizations in a WidgetsApp', (WidgetTester tester) async {
final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[ final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[
new SyncTestLocalizationsDelegate(), new SyncTestLocalizationsDelegate(),
const DefaultWidgetsLocalizationsDelegate(), DefaultWidgetsLocalizations.delegate,
]; ];
Future<Null> pumpTest(Locale locale) async { Future<Null> pumpTest(Locale locale) async {
@ -349,7 +337,7 @@ void main() {
locale: const Locale('en', 'GB'), locale: const Locale('en', 'GB'),
delegates: <LocalizationsDelegate<dynamic>>[ delegates: <LocalizationsDelegate<dynamic>>[
new SyncTestLocalizationsDelegate(), new SyncTestLocalizationsDelegate(),
const DefaultWidgetsLocalizationsDelegate(), DefaultWidgetsLocalizations.delegate,
], ],
// Create a new context within the en_GB Localization // Create a new context within the en_GB Localization
child: new Builder( child: new Builder(
@ -476,6 +464,9 @@ void main() {
await tester.pumpWidget( await tester.pumpWidget(
buildFrame( buildFrame(
delegates: const <LocalizationsDelegate<dynamic>>[
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: const <Locale>[ supportedLocales: const <Locale>[
const Locale('en', 'GB'), const Locale('en', 'GB'),
const Locale('ar', 'EG'), const Locale('ar', 'EG'),
@ -557,6 +548,9 @@ void main() {
buildFrame( buildFrame(
// Accept whatever locale we're given // Accept whatever locale we're given
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale, localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
delegates: const <LocalizationsDelegate<dynamic>>[
GlobalWidgetsLocalizations.delegate,
],
buildContent: (BuildContext context) { buildContent: (BuildContext context) {
return new Localizations.override( return new Localizations.override(
context: context, context: context,

View File

@ -27,7 +27,7 @@ dependencies:
args: 0.13.7 # TRANSITIVE DEPENDENCY args: 0.13.7 # TRANSITIVE DEPENDENCY
async: 1.13.3 # 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 boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
charcode: 1.1.1 # TRANSITIVE DEPENDENCY charcode: 1.1.1 # TRANSITIVE DEPENDENCY
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY

View File

@ -48,7 +48,7 @@ dev_dependencies:
mockito: 2.2.0 mockito: 2.2.0
async: 1.13.3 # 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 boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
charcode: 1.1.1 # TRANSITIVE DEPENDENCY charcode: 1.1.1 # TRANSITIVE DEPENDENCY
convert: 2.0.1 # TRANSITIVE DEPENDENCY convert: 2.0.1 # TRANSITIVE DEPENDENCY