flutter_localization optional package (#12410)
This commit is contained in:
parent
f07170b45b
commit
c3d56b1dad
@ -7,7 +7,7 @@ dependencies:
|
|||||||
|
|
||||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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'));
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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'),
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
@ -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());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,193 +0,0 @@
|
|||||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('$MaterialLocalizations localizes text inside the tree', (WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(new MaterialApp(
|
|
||||||
home: new ListView(
|
|
||||||
children: <Widget>[
|
|
||||||
new LocalizationTracker(key: const ValueKey<String>('outer')),
|
|
||||||
new Localizations(
|
|
||||||
locale: const Locale('zh', 'CN'),
|
|
||||||
delegates: <LocalizationsDelegate<dynamic>>[
|
|
||||||
new _MaterialLocalizationsDelegate(
|
|
||||||
new DefaultMaterialLocalizations(const Locale('zh', 'CN')),
|
|
||||||
),
|
|
||||||
const DefaultWidgetsLocalizationsDelegate(),
|
|
||||||
],
|
|
||||||
child: new LocalizationTracker(key: const ValueKey<String>('inner')),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
));
|
|
||||||
|
|
||||||
final LocalizationTrackerState outerTracker = tester.state(find.byKey(const ValueKey<String>('outer')));
|
|
||||||
expect(outerTracker.captionFontSize, 12.0);
|
|
||||||
final LocalizationTrackerState innerTracker = tester.state(find.byKey(const ValueKey<String>('inner')));
|
|
||||||
expect(innerTracker.captionFontSize, 13.0);
|
|
||||||
});
|
|
||||||
|
|
||||||
group(DefaultMaterialLocalizations, () {
|
|
||||||
test('uses exact locale when exists', () {
|
|
||||||
final DefaultMaterialLocalizations localizations = new DefaultMaterialLocalizations(const Locale('pt', 'PT'));
|
|
||||||
expect(localizations.formatDecimal(10000), '10\u00A0000');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('falls back to language code when exact locale is missing', () {
|
|
||||||
final DefaultMaterialLocalizations localizations = new DefaultMaterialLocalizations(const Locale('pt', 'XX'));
|
|
||||||
expect(localizations.formatDecimal(10000), '10.000');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('falls back to default format when neither language code nor exact locale are available', () {
|
|
||||||
final DefaultMaterialLocalizations localizations = new DefaultMaterialLocalizations(const Locale('xx', 'XX'));
|
|
||||||
expect(localizations.formatDecimal(10000), '10,000');
|
|
||||||
});
|
|
||||||
|
|
||||||
group('formatHour', () {
|
|
||||||
test('formats h', () {
|
|
||||||
DefaultMaterialLocalizations localizations;
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('en', 'US'));
|
|
||||||
expect(localizations.formatHour(const TimeOfDay(hour: 10, minute: 0)), '10');
|
|
||||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '8');
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('ar', ''));
|
|
||||||
expect(localizations.formatHour(const TimeOfDay(hour: 10, minute: 0)), '١٠');
|
|
||||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '٨');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('formats HH', () {
|
|
||||||
DefaultMaterialLocalizations localizations;
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('de', ''));
|
|
||||||
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '09');
|
|
||||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('en', 'GB'));
|
|
||||||
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '09');
|
|
||||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('formats H', () {
|
|
||||||
DefaultMaterialLocalizations localizations;
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('es', ''));
|
|
||||||
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '9');
|
|
||||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('fa', ''));
|
|
||||||
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '۹');
|
|
||||||
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '۲۰');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group('formatMinute', () {
|
|
||||||
test('formats English', () {
|
|
||||||
final DefaultMaterialLocalizations localizations = new DefaultMaterialLocalizations(const Locale('en', 'US'));
|
|
||||||
expect(localizations.formatMinute(const TimeOfDay(hour: 1, minute: 32)), '32');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('formats Arabic', () {
|
|
||||||
final DefaultMaterialLocalizations localizations = new DefaultMaterialLocalizations(const Locale('ar', ''));
|
|
||||||
expect(localizations.formatMinute(const TimeOfDay(hour: 1, minute: 32)), '٣٢');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group('formatTimeOfDay', () {
|
|
||||||
test('formats ${TimeOfDayFormat.h_colon_mm_space_a}', () {
|
|
||||||
DefaultMaterialLocalizations localizations;
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('ar', ''));
|
|
||||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '٩:٣٢ ص');
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('en', ''));
|
|
||||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32 AM');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('formats ${TimeOfDayFormat.HH_colon_mm}', () {
|
|
||||||
DefaultMaterialLocalizations localizations;
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('de', ''));
|
|
||||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09:32');
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('en', 'ZA'));
|
|
||||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09:32');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('formats ${TimeOfDayFormat.H_colon_mm}', () {
|
|
||||||
DefaultMaterialLocalizations localizations;
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('es', ''));
|
|
||||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32');
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('ja', ''));
|
|
||||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('formats ${TimeOfDayFormat.frenchCanadian}', () {
|
|
||||||
DefaultMaterialLocalizations localizations;
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('fr', 'CA'));
|
|
||||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09 h 32');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('formats ${TimeOfDayFormat.a_space_h_colon_mm}', () {
|
|
||||||
DefaultMaterialLocalizations localizations;
|
|
||||||
|
|
||||||
localizations = new DefaultMaterialLocalizations(const Locale('zh', ''));
|
|
||||||
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '上午 9:32');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class LocalizationTracker extends StatefulWidget {
|
|
||||||
LocalizationTracker({Key key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => new LocalizationTrackerState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class LocalizationTrackerState extends State<LocalizationTracker> {
|
|
||||||
double captionFontSize;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
captionFontSize = Theme.of(context).textTheme.caption.fontSize;
|
|
||||||
return new Container();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same as _MaterialLocalizationsDelegate in widgets/app.dart
|
|
||||||
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
|
|
||||||
const _MaterialLocalizationsDelegate(this.localizations);
|
|
||||||
|
|
||||||
final MaterialLocalizations localizations;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MaterialLocalizations> load(Locale locale) {
|
|
||||||
return new SynchronousFuture<MaterialLocalizations>(localizations);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same as _WidgetsLocalizationsDelegate in widgets/app.dart
|
|
||||||
class DefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
|
|
||||||
const DefaultWidgetsLocalizationsDelegate();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<WidgetsLocalizations> load(Locale locale) {
|
|
||||||
return new SynchronousFuture<WidgetsLocalizations>(new DefaultWidgetsLocalizations(locale));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldReload(DefaultWidgetsLocalizationsDelegate old) => false;
|
|
||||||
}
|
|
@ -205,78 +205,4 @@ void main() {
|
|||||||
expect(feedback.hapticCount, 3);
|
expect(feedback.hapticCount, 3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('localization', () {
|
|
||||||
testWidgets('can localize the header in all known formats', (WidgetTester tester) async {
|
|
||||||
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
|
|
||||||
final Map<Locale, List<String>> locales = <Locale, List<String>>{
|
|
||||||
const Locale('en', 'US'): const <String>['hour h', 'string :', 'minute', 'period'], //'h:mm a'
|
|
||||||
const Locale('en', 'GB'): const <String>['hour HH', 'string :', 'minute'], //'HH:mm'
|
|
||||||
const Locale('es', 'ES'): const <String>['hour H', 'string :', 'minute'], //'H:mm'
|
|
||||||
const Locale('fr', 'CA'): const <String>['hour HH', 'string h', 'minute'], //'HH \'h\' mm'
|
|
||||||
const Locale('zh', 'ZH'): const <String>['period', 'hour h', 'string :', 'minute'], //'ah:mm'
|
|
||||||
};
|
|
||||||
|
|
||||||
for (Locale locale in locales.keys) {
|
|
||||||
final Offset center = await startPicker(tester, (TimeOfDay time) { }, locale: locale);
|
|
||||||
final List<String> actual = <String>[];
|
|
||||||
tester.element(find.byType(CustomMultiChildLayout)).visitChildren((Element child) {
|
|
||||||
final LayoutId layout = child.widget;
|
|
||||||
final String fragmentType = '${layout.child.runtimeType}';
|
|
||||||
final dynamic widget = layout.child;
|
|
||||||
if (fragmentType == '_MinuteControl') {
|
|
||||||
actual.add('minute');
|
|
||||||
} else if (fragmentType == '_DayPeriodControl') {
|
|
||||||
actual.add('period');
|
|
||||||
} else if (fragmentType == '_HourControl') {
|
|
||||||
actual.add('hour ${widget.hourFormat.toString().split('.').last}');
|
|
||||||
} else if (fragmentType == '_StringFragment') {
|
|
||||||
actual.add('string ${widget.value}');
|
|
||||||
} else {
|
|
||||||
fail('Unsupported fragment type: $fragmentType');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
expect(actual, locales[locale]);
|
|
||||||
await tester.tapAt(new Offset(center.dx, center.dy - 50.0));
|
|
||||||
await finishPicker(tester);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('uses single-ring 12-hour dial for h hour format', (WidgetTester tester) async {
|
|
||||||
// Tap along the segment stretching from the center to the edge at
|
|
||||||
// 12:00 AM position. Because there's only one ring, no matter where you
|
|
||||||
// tap the time will be the same. See the 24-hour dial test that behaves
|
|
||||||
// differently.
|
|
||||||
for (int i = 1; i < 10; i++) {
|
|
||||||
TimeOfDay result;
|
|
||||||
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
|
||||||
final Size size = tester.getSize(find.byKey(const Key('time-picker-dial')));
|
|
||||||
final double dy = (size.height / 2.0 / 10) * i;
|
|
||||||
await tester.tapAt(new Offset(center.dx, center.dy - dy));
|
|
||||||
await finishPicker(tester);
|
|
||||||
expect(result, equals(const TimeOfDay(hour: 0, minute: 0)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('uses two-ring 24-hour dial for H and HH hour formats', (WidgetTester tester) async {
|
|
||||||
const List<Locale> locales = const <Locale>[
|
|
||||||
const Locale('en', 'GB'), // HH
|
|
||||||
const Locale('es', 'ES'), // H
|
|
||||||
];
|
|
||||||
for (Locale locale in locales) {
|
|
||||||
// Tap along the segment stretching from the center to the edge at
|
|
||||||
// 12:00 AM position. There are two rings. At ~70% mark, the ring
|
|
||||||
// switches between inner ring and outer ring.
|
|
||||||
for (int i = 1; i < 10; i++) {
|
|
||||||
TimeOfDay result;
|
|
||||||
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; }, locale: locale);
|
|
||||||
final Size size = tester.getSize(find.byKey(const Key('time-picker-dial')));
|
|
||||||
final double dy = (size.height / 2.0 / 10) * i;
|
|
||||||
await tester.tapAt(new Offset(center.dx, center.dy - dy));
|
|
||||||
await finishPicker(tester);
|
|
||||||
expect(result, equals(new TimeOfDay(hour: i < 7 ? 12 : 0, minute: 0)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ dev_dependencies:
|
|||||||
|
|
||||||
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
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
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
/// Localizations for the Flutter library
|
||||||
|
library flutter_localizations;
|
||||||
|
|
||||||
|
export 'src/material_localizations.dart' show GlobalMaterialLocalizations;
|
||||||
|
export 'src/widgets_localizations.dart' show GlobalWidgetsLocalizations;
|
@ -1,12 +1,12 @@
|
|||||||
# Material Library Internationalization
|
# Material Library Localizations
|
||||||
|
|
||||||
The `.arb` files in this directory contain localized values (primarily
|
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.
|
@ -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"下午",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,431 @@
|
|||||||
|
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart' as intl;
|
||||||
|
import 'package:intl/date_symbol_data_local.dart' as intl_local_date_data;
|
||||||
|
|
||||||
|
import 'l10n/localizations.dart';
|
||||||
|
import 'widgets_localizations.dart';
|
||||||
|
|
||||||
|
/// Localized strings for the material widgets.
|
||||||
|
///
|
||||||
|
/// To include the localizations provided by this class in a [MaterialApp],
|
||||||
|
/// add [GlobalMaterialLocalizations.delegates] to
|
||||||
|
/// [MaterialApp.localizationsDelegates], and specify the locales your
|
||||||
|
/// app supports with [MaterialApp.supportedLocales]:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// new MaterialApp(
|
||||||
|
/// localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||||
|
/// supportedLocales: [
|
||||||
|
/// const Locale('en', 'US'), // English
|
||||||
|
/// const Locale('he', 'IL'), // Hebrew
|
||||||
|
/// // ...
|
||||||
|
/// ],
|
||||||
|
/// // ...
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This class supports locales with the following [Locale.languageCode]s:
|
||||||
|
///
|
||||||
|
/// * ar - Arabic
|
||||||
|
/// * de - German
|
||||||
|
/// * en - English
|
||||||
|
/// * es - Spanish
|
||||||
|
/// * fa - Farsi
|
||||||
|
/// * fr - French
|
||||||
|
/// * he - Hebrew
|
||||||
|
/// * it - Italian
|
||||||
|
/// * ja - Japanese
|
||||||
|
/// * ps - Pashto
|
||||||
|
/// * pt - Portugese
|
||||||
|
/// * ru - Russian
|
||||||
|
/// * sd - Sindhi
|
||||||
|
/// * ur - Urdu
|
||||||
|
/// * zh - Simplified Chinese
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * The Flutter Internationalization Tutorial,
|
||||||
|
/// <https://flutter.io/tutorials/internationalization/>.
|
||||||
|
/// * [DefaultMaterialLocalizations], which only provides US English translations.
|
||||||
|
class GlobalMaterialLocalizations implements MaterialLocalizations {
|
||||||
|
/// Constructs an object that defines the material widgets' localized strings
|
||||||
|
/// for the given `locale`.
|
||||||
|
///
|
||||||
|
/// [LocalizationsDelegate] implementations typically call the static [load]
|
||||||
|
/// function, rather than constructing this class directly.
|
||||||
|
GlobalMaterialLocalizations(this.locale)
|
||||||
|
: assert(locale != null),
|
||||||
|
this._localeName = _computeLocaleName(locale) {
|
||||||
|
_loadDateIntlDataIfNotLoaded();
|
||||||
|
|
||||||
|
if (localizations.containsKey(locale.languageCode))
|
||||||
|
_nameToValue.addAll(localizations[locale.languageCode]);
|
||||||
|
if (localizations.containsKey(_localeName))
|
||||||
|
_nameToValue.addAll(localizations[_localeName]);
|
||||||
|
|
||||||
|
const String kMediumDatePattern = 'E, MMM\u00a0d';
|
||||||
|
if (intl.DateFormat.localeExists(_localeName)) {
|
||||||
|
_fullYearFormat = new intl.DateFormat.y(_localeName);
|
||||||
|
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern, _localeName);
|
||||||
|
_yearMonthFormat = new intl.DateFormat('yMMMM', _localeName);
|
||||||
|
} else if (intl.DateFormat.localeExists(locale.languageCode)) {
|
||||||
|
_fullYearFormat = new intl.DateFormat.y(locale.languageCode);
|
||||||
|
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern, locale.languageCode);
|
||||||
|
_yearMonthFormat = new intl.DateFormat('yMMMM', locale.languageCode);
|
||||||
|
} else {
|
||||||
|
_fullYearFormat = new intl.DateFormat.y();
|
||||||
|
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern);
|
||||||
|
_yearMonthFormat = new intl.DateFormat('yMMMM');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intl.NumberFormat.localeExists(_localeName)) {
|
||||||
|
_decimalFormat = new intl.NumberFormat.decimalPattern(_localeName);
|
||||||
|
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00', _localeName);
|
||||||
|
} else if (intl.NumberFormat.localeExists(locale.languageCode)) {
|
||||||
|
_decimalFormat = new intl.NumberFormat.decimalPattern(locale.languageCode);
|
||||||
|
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00', locale.languageCode);
|
||||||
|
} else {
|
||||||
|
_decimalFormat = new intl.NumberFormat.decimalPattern();
|
||||||
|
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The locale for which the values of this class's localized resources
|
||||||
|
/// have been translated.
|
||||||
|
final Locale locale;
|
||||||
|
|
||||||
|
final String _localeName;
|
||||||
|
|
||||||
|
final Map<String, String> _nameToValue = <String, String>{};
|
||||||
|
|
||||||
|
intl.NumberFormat _decimalFormat;
|
||||||
|
|
||||||
|
intl.NumberFormat _twoDigitZeroPaddedFormat;
|
||||||
|
|
||||||
|
intl.DateFormat _fullYearFormat;
|
||||||
|
|
||||||
|
intl.DateFormat _mediumDateFormat;
|
||||||
|
|
||||||
|
intl.DateFormat _yearMonthFormat;
|
||||||
|
|
||||||
|
static String _computeLocaleName(Locale locale) {
|
||||||
|
final String localeName = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
|
||||||
|
return intl.Intl.canonicalizedLocale(localeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(hmuller): the rules for mapping from an integer value to
|
||||||
|
// "one" or "two" etc. are locale specific and an additional "few" category
|
||||||
|
// is needed. See http://cldr.unicode.org/index/cldr-spec/plural-rules
|
||||||
|
String _nameToPluralValue(int count, String key) {
|
||||||
|
String text;
|
||||||
|
if (count == 0)
|
||||||
|
text = _nameToValue['${key}Zero'];
|
||||||
|
else if (count == 1)
|
||||||
|
text = _nameToValue['${key}One'];
|
||||||
|
else if (count == 2)
|
||||||
|
text = _nameToValue['${key}Two'];
|
||||||
|
else if (count > 2)
|
||||||
|
text = _nameToValue['${key}Many'];
|
||||||
|
text ??= _nameToValue['${key}Other'];
|
||||||
|
assert(text != null);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String formatHour(TimeOfDay timeOfDay) {
|
||||||
|
switch (hourFormat(of: timeOfDayFormat)) {
|
||||||
|
case HourFormat.HH:
|
||||||
|
return _twoDigitZeroPaddedFormat.format(timeOfDay.hour);
|
||||||
|
case HourFormat.H:
|
||||||
|
return formatDecimal(timeOfDay.hour);
|
||||||
|
case HourFormat.h:
|
||||||
|
final int hour = timeOfDay.hourOfPeriod;
|
||||||
|
return formatDecimal(hour == 0 ? 12 : hour);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String formatMinute(TimeOfDay timeOfDay) {
|
||||||
|
return _twoDigitZeroPaddedFormat.format(timeOfDay.minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String formatYear(DateTime date) {
|
||||||
|
return _fullYearFormat.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String formatMediumDate(DateTime date) {
|
||||||
|
return _mediumDateFormat.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String formatMonthYear(DateTime date) {
|
||||||
|
return _yearMonthFormat.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> get narrowWeekdays {
|
||||||
|
return _fullYearFormat.dateSymbols.NARROWWEEKDAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get firstDayOfWeekIndex => (_fullYearFormat.dateSymbols.FIRSTDAYOFWEEK + 1) % 7;
|
||||||
|
|
||||||
|
/// Formats a [number] using local decimal number format.
|
||||||
|
///
|
||||||
|
/// Inserts locale-appropriate thousands separator, if necessary.
|
||||||
|
String formatDecimal(int number) {
|
||||||
|
return _decimalFormat.format(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String formatTimeOfDay(TimeOfDay timeOfDay) {
|
||||||
|
// Not using intl.DateFormat for two reasons:
|
||||||
|
//
|
||||||
|
// - DateFormat supports more formats than our material time picker does,
|
||||||
|
// and we want to be consistent across time picker format and the string
|
||||||
|
// formatting of the time of day.
|
||||||
|
// - DateFormat operates on DateTime, which is sensitive to time eras and
|
||||||
|
// time zones, while here we want to format hour and minute within one day
|
||||||
|
// no matter what date the day falls on.
|
||||||
|
switch (timeOfDayFormat) {
|
||||||
|
case TimeOfDayFormat.h_colon_mm_space_a:
|
||||||
|
return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)} ${_formatDayPeriod(timeOfDay)}';
|
||||||
|
case TimeOfDayFormat.H_colon_mm:
|
||||||
|
case TimeOfDayFormat.HH_colon_mm:
|
||||||
|
return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)}';
|
||||||
|
case TimeOfDayFormat.HH_dot_mm:
|
||||||
|
return '${formatHour(timeOfDay)}.${formatMinute(timeOfDay)}';
|
||||||
|
case TimeOfDayFormat.a_space_h_colon_mm:
|
||||||
|
return '${_formatDayPeriod(timeOfDay)} ${formatHour(timeOfDay)}:${formatMinute(timeOfDay)}';
|
||||||
|
case TimeOfDayFormat.frenchCanadian:
|
||||||
|
return '${formatHour(timeOfDay)} h ${formatMinute(timeOfDay)}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDayPeriod(TimeOfDay timeOfDay) {
|
||||||
|
switch (timeOfDay.period) {
|
||||||
|
case DayPeriod.am:
|
||||||
|
return anteMeridiemAbbreviation;
|
||||||
|
case DayPeriod.pm:
|
||||||
|
return postMeridiemAbbreviation;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openAppDrawerTooltip => _nameToValue['openAppDrawerTooltip'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backButtonTooltip => _nameToValue['backButtonTooltip'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get closeButtonTooltip => _nameToValue['closeButtonTooltip'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nextMonthTooltip => _nameToValue['nextMonthTooltip'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previousMonthTooltip => _nameToValue['previousMonthTooltip'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nextPageTooltip => _nameToValue['nextPageTooltip'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get previousPageTooltip => _nameToValue['previousPageTooltip'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showMenuTooltip => _nameToValue['showMenuTooltip'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String aboutListTileTitle(String applicationName) {
|
||||||
|
final String text = _nameToValue['aboutListTileTitle'];
|
||||||
|
return text.replaceFirst(r'$applicationName', applicationName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get licensesPageTitle => _nameToValue['licensesPageTitle'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String pageRowsInfoTitle(int firstRow, int lastRow, int rowCount, bool rowCountIsApproximate) {
|
||||||
|
String text = rowCountIsApproximate ? _nameToValue['pageRowsInfoTitleApproximate'] : null;
|
||||||
|
text ??= _nameToValue['pageRowsInfoTitle'];
|
||||||
|
assert(text != null, 'A $locale localization was not found for pageRowsInfoTitle or pageRowsInfoTitleApproximate');
|
||||||
|
// TODO(hansmuller): this could be more efficient.
|
||||||
|
return text
|
||||||
|
.replaceFirst(r'$firstRow', formatDecimal(firstRow))
|
||||||
|
.replaceFirst(r'$lastRow', formatDecimal(lastRow))
|
||||||
|
.replaceFirst(r'$rowCount', formatDecimal(rowCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get rowsPerPageTitle => _nameToValue['rowsPerPageTitle'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String selectedRowCountTitle(int selectedRowCount) {
|
||||||
|
return _nameToPluralValue(selectedRowCount, 'selectedRowCountTitle') // asserts on no match
|
||||||
|
.replaceFirst(r'$selectedRowCount', formatDecimal(selectedRowCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancelButtonLabel => _nameToValue['cancelButtonLabel'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get closeButtonLabel => _nameToValue['closeButtonLabel'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get continueButtonLabel => _nameToValue['continueButtonLabel'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get copyButtonLabel => _nameToValue['copyButtonLabel'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cutButtonLabel => _nameToValue['cutButtonLabel'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get okButtonLabel => _nameToValue['okButtonLabel'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pasteButtonLabel => _nameToValue['pasteButtonLabel'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectAllButtonLabel => _nameToValue['selectAllButtonLabel'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewLicensesButtonLabel => _nameToValue['viewLicensesButtonLabel'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get anteMeridiemAbbreviation => _nameToValue['anteMeridiemAbbreviation'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get postMeridiemAbbreviation => _nameToValue['postMeridiemAbbreviation'];
|
||||||
|
|
||||||
|
/// The [TimeOfDayFormat] corresponding to one of the following supported
|
||||||
|
/// patterns:
|
||||||
|
///
|
||||||
|
/// * `HH:mm`
|
||||||
|
/// * `HH.mm`
|
||||||
|
/// * `HH 'h' mm`
|
||||||
|
/// * `HH:mm น.`
|
||||||
|
/// * `H:mm`
|
||||||
|
/// * `h:mm a`
|
||||||
|
/// * `a h:mm`
|
||||||
|
/// * `ah:mm`
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * http://demo.icu-project.org/icu-bin/locexp?d_=en&_=en_US shows the
|
||||||
|
/// short time pattern used in locale en_US
|
||||||
|
@override
|
||||||
|
TimeOfDayFormat get timeOfDayFormat {
|
||||||
|
final String icuShortTimePattern = _nameToValue['timeOfDayFormat'];
|
||||||
|
|
||||||
|
assert(() {
|
||||||
|
if (!_icuTimeOfDayToEnum.containsKey(icuShortTimePattern)) {
|
||||||
|
throw new FlutterError(
|
||||||
|
'"$icuShortTimePattern" is not one of the ICU short time patterns '
|
||||||
|
'supported by the material library. Here is the list of supported '
|
||||||
|
'patterns:\n ' +
|
||||||
|
_icuTimeOfDayToEnum.keys.join('\n ')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return _icuTimeOfDayToEnum[icuShortTimePattern];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Looks up text geometry defined in [MaterialTextGeometry].
|
||||||
|
@override
|
||||||
|
TextTheme get localTextGeometry => MaterialTextGeometry.forScriptCategory(_nameToValue["scriptCategory"]);
|
||||||
|
|
||||||
|
/// Creates an object that provides localized resource values for the
|
||||||
|
/// for the widgets of the material library.
|
||||||
|
///
|
||||||
|
/// This method is typically used to create a [LocalizationsDelegate].
|
||||||
|
/// The [MaterialApp] does so by default.
|
||||||
|
static Future<MaterialLocalizations> load(Locale locale) {
|
||||||
|
return new SynchronousFuture<MaterialLocalizations>(new GlobalMaterialLocalizations(locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [LocalizationsDelegate] that uses [GlobalMaterialLocalizations.load]
|
||||||
|
/// to create an instance of this class.
|
||||||
|
///
|
||||||
|
/// Most internationlized apps will use [GlobalMaterialLocalizations.delegates]
|
||||||
|
/// as the value of [MaterialApp.localizationsDelegates] to include
|
||||||
|
/// the localizations for both the material and widget libraries.
|
||||||
|
static const LocalizationsDelegate<MaterialLocalizations> delegate = const _MaterialLocalizationsDelegate();
|
||||||
|
|
||||||
|
/// A value for [MaterialApp.localizationsDelegates] that's typically used by
|
||||||
|
/// internationalized apps.
|
||||||
|
///
|
||||||
|
/// To include the localizations provided by this class and by
|
||||||
|
/// [GlobalWidgetsLocalizations] in a [MaterialApp],
|
||||||
|
/// use [GlobalMaterialLocalizations.delegates] as the value of
|
||||||
|
/// [MaterialApp.localizationsDelegates], and specify the locales your
|
||||||
|
/// app supports with [MaterialApp.supportedLocales]:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// new MaterialApp(
|
||||||
|
/// localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||||
|
/// supportedLocales: [
|
||||||
|
/// const Locale('en', 'US'), // English
|
||||||
|
/// const Locale('he', 'IL'), // Hebrew
|
||||||
|
/// ],
|
||||||
|
/// // ...
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
static const List<LocalizationsDelegate<dynamic>> delegates = const <LocalizationsDelegate<dynamic>>[
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Map<String, TimeOfDayFormat> _icuTimeOfDayToEnum = const <String, TimeOfDayFormat>{
|
||||||
|
'HH:mm': TimeOfDayFormat.HH_colon_mm,
|
||||||
|
'HH.mm': TimeOfDayFormat.HH_dot_mm,
|
||||||
|
"HH 'h' mm": TimeOfDayFormat.frenchCanadian,
|
||||||
|
'HH:mm น.': TimeOfDayFormat.HH_colon_mm,
|
||||||
|
'H:mm': TimeOfDayFormat.H_colon_mm,
|
||||||
|
'h:mm a': TimeOfDayFormat.h_colon_mm_space_a,
|
||||||
|
'a h:mm': TimeOfDayFormat.a_space_h_colon_mm,
|
||||||
|
'ah:mm': TimeOfDayFormat.a_space_h_colon_mm,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Tracks if date i18n data has been loaded.
|
||||||
|
bool _dateIntlDataInitialized = false;
|
||||||
|
|
||||||
|
/// Loads i18n data for dates if it hasn't be loaded yet.
|
||||||
|
///
|
||||||
|
/// Only the first invocation of this function has the effect of loading the
|
||||||
|
/// data. Subsequent invocations have no effect.
|
||||||
|
void _loadDateIntlDataIfNotLoaded() {
|
||||||
|
if (!_dateIntlDataInitialized) {
|
||||||
|
// The returned Future is intentionally dropped on the floor. The
|
||||||
|
// function only returns it to be compatible with the async counterparts.
|
||||||
|
// The Future has no value otherwise.
|
||||||
|
intl_local_date_data.initializeDateFormatting();
|
||||||
|
_dateIntlDataInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
|
||||||
|
const _MaterialLocalizationsDelegate();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MaterialLocalizations> load(Locale locale) => GlobalMaterialLocalizations.load(locale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
/// Localized values for widgets.
|
||||||
|
///
|
||||||
|
/// Currently this class just maps [locale] to [textDirection]. All locales
|
||||||
|
/// are [TextDirection.ltr] except for locales with the following
|
||||||
|
/// [Locale.languageCode] values, which are [TextDirection.rtl]:
|
||||||
|
///
|
||||||
|
/// * ar - Arabic
|
||||||
|
/// * fa - Farsi
|
||||||
|
/// * he - Hebrew
|
||||||
|
/// * ps - Pashto
|
||||||
|
/// * sd - Sindhi
|
||||||
|
/// * ur - Urdu
|
||||||
|
class GlobalWidgetsLocalizations implements WidgetsLocalizations {
|
||||||
|
/// Construct an object that defines the localized values for the widgets
|
||||||
|
/// library for the given `locale`.
|
||||||
|
///
|
||||||
|
/// [LocalizationsDelegate] implementations typically call the static [load]
|
||||||
|
/// function, rather than constructing this class directly.
|
||||||
|
GlobalWidgetsLocalizations(this.locale) {
|
||||||
|
final String language = locale.languageCode.toLowerCase();
|
||||||
|
_textDirection = _rtlLanguages.contains(language) ? TextDirection.rtl : TextDirection.ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See http://en.wikipedia.org/wiki/Right-to-left
|
||||||
|
static const List<String> _rtlLanguages = const <String>[
|
||||||
|
'ar', // Arabic
|
||||||
|
'fa', // Farsi
|
||||||
|
'he', // Hebrew
|
||||||
|
'ps', // Pashto
|
||||||
|
'sd', // Sindhi
|
||||||
|
'ur', // Urdu
|
||||||
|
];
|
||||||
|
|
||||||
|
/// The locale for which the values of this class's localized resources
|
||||||
|
/// have been translated.
|
||||||
|
final Locale locale;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextDirection get textDirection => _textDirection;
|
||||||
|
TextDirection _textDirection;
|
||||||
|
|
||||||
|
/// Creates an object that provides localized resource values for the
|
||||||
|
/// lowest levels of the Flutter framework.
|
||||||
|
///
|
||||||
|
/// This method is typically used to create a [LocalizationsDelegate].
|
||||||
|
/// The [WidgetsApp] does so by default.
|
||||||
|
static Future<WidgetsLocalizations> load(Locale locale) {
|
||||||
|
return new SynchronousFuture<WidgetsLocalizations>(new GlobalWidgetsLocalizations(locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [LocalizationsDelegate] that uses [GlobalWidgetsLocalizations.load]
|
||||||
|
/// to create an instance of this class.
|
||||||
|
///
|
||||||
|
/// [WidgetsApp] automatically adds this value to [WidgetApp.localizationsDelegates].
|
||||||
|
static const LocalizationsDelegate<WidgetsLocalizations> delegate = const _WidgetsLocalizationsDelegate();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
|
||||||
|
const _WidgetsLocalizationsDelegate();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<WidgetsLocalizations> load(Locale locale) => GlobalWidgetsLocalizations.load(locale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldReload(_WidgetsLocalizationsDelegate old) => false;
|
||||||
|
}
|
63
packages/flutter_localizations/pubspec.yaml
Normal file
63
packages/flutter_localizations/pubspec.yaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
name: flutter_localizations
|
||||||
|
version: 0.0.1-dev
|
||||||
|
dependencies:
|
||||||
|
# To update these, use "flutter update-packages --force-upgrade".
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
intl: 0.15.1
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
mockito: 2.2.0
|
||||||
|
|
||||||
|
args: 0.13.7 # TRANSITIVE DEPENDENCY
|
||||||
|
async: 1.13.3 # TRANSITIVE DEPENDENCY
|
||||||
|
barback: 0.15.2+13 # TRANSITIVE DEPENDENCY
|
||||||
|
boolean_selector: 1.0.2 # TRANSITIVE DEPENDENCY
|
||||||
|
charcode: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||||
|
cli_util: 0.1.2+1 # TRANSITIVE DEPENDENCY
|
||||||
|
collection: 1.14.3 # TRANSITIVE DEPENDENCY
|
||||||
|
convert: 2.0.1 # TRANSITIVE DEPENDENCY
|
||||||
|
crypto: 2.0.2+1 # TRANSITIVE DEPENDENCY
|
||||||
|
csslib: 0.14.1 # TRANSITIVE DEPENDENCY
|
||||||
|
dart_style: 1.0.8 # TRANSITIVE DEPENDENCY
|
||||||
|
glob: 1.1.5 # TRANSITIVE DEPENDENCY
|
||||||
|
html: 0.13.2 # TRANSITIVE DEPENDENCY
|
||||||
|
http: 0.11.3+14 # TRANSITIVE DEPENDENCY
|
||||||
|
http_multi_server: 2.0.4 # TRANSITIVE DEPENDENCY
|
||||||
|
http_parser: 3.1.1 # TRANSITIVE DEPENDENCY
|
||||||
|
intl_translation: 0.15.0 # TRANSITIVE DEPENDENCY
|
||||||
|
isolate: 1.1.0 # TRANSITIVE DEPENDENCY
|
||||||
|
js: 0.6.1 # TRANSITIVE DEPENDENCY
|
||||||
|
logging: 0.11.3+1 # TRANSITIVE DEPENDENCY
|
||||||
|
matcher: 0.12.1+4 # TRANSITIVE DEPENDENCY
|
||||||
|
meta: 1.1.1 # TRANSITIVE DEPENDENCY
|
||||||
|
mime: 0.9.3 # TRANSITIVE DEPENDENCY
|
||||||
|
node_preamble: 1.4.0 # TRANSITIVE DEPENDENCY
|
||||||
|
package_config: 1.0.3 # TRANSITIVE DEPENDENCY
|
||||||
|
package_resolver: 1.0.2 # TRANSITIVE DEPENDENCY
|
||||||
|
path: 1.4.2 # TRANSITIVE DEPENDENCY
|
||||||
|
petitparser: 1.6.1 # TRANSITIVE DEPENDENCY
|
||||||
|
plugin: 0.2.0+2 # TRANSITIVE DEPENDENCY
|
||||||
|
pool: 1.3.3 # TRANSITIVE DEPENDENCY
|
||||||
|
pub_semver: 1.3.2 # TRANSITIVE DEPENDENCY
|
||||||
|
shelf: 0.7.0 # TRANSITIVE DEPENDENCY
|
||||||
|
shelf_packages_handler: 1.0.3 # TRANSITIVE DEPENDENCY
|
||||||
|
shelf_static: 0.2.5 # TRANSITIVE DEPENDENCY
|
||||||
|
shelf_web_socket: 0.2.2 # TRANSITIVE DEPENDENCY
|
||||||
|
source_map_stack_trace: 1.1.4 # TRANSITIVE DEPENDENCY
|
||||||
|
source_maps: 0.10.4 # TRANSITIVE DEPENDENCY
|
||||||
|
source_span: 1.4.0 # TRANSITIVE DEPENDENCY
|
||||||
|
stack_trace: 1.8.2 # TRANSITIVE DEPENDENCY
|
||||||
|
stream_channel: 1.6.2 # TRANSITIVE DEPENDENCY
|
||||||
|
string_scanner: 1.0.2 # TRANSITIVE DEPENDENCY
|
||||||
|
term_glyph: 1.0.0 # TRANSITIVE DEPENDENCY
|
||||||
|
test: 0.12.24+8 # TRANSITIVE DEPENDENCY
|
||||||
|
typed_data: 1.1.4 # TRANSITIVE DEPENDENCY
|
||||||
|
utf: 0.9.0+3 # TRANSITIVE DEPENDENCY
|
||||||
|
vector_math: 2.0.5 # TRANSITIVE DEPENDENCY
|
||||||
|
watcher: 0.9.7+4 # TRANSITIVE DEPENDENCY
|
||||||
|
web_socket_channel: 1.0.6 # TRANSITIVE DEPENDENCY
|
||||||
|
yaml: 2.1.13 # TRANSITIVE DEPENDENCY
|
79
packages/flutter_localizations/test/basics_test.dart
Normal file
79
packages/flutter_localizations/test/basics_test.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Nested Localizations', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(new MaterialApp( // Creates the outer Localizations widget.
|
||||||
|
home: new ListView(
|
||||||
|
children: <Widget>[
|
||||||
|
new LocalizationTracker(key: const ValueKey<String>('outer')),
|
||||||
|
new Localizations(
|
||||||
|
locale: const Locale('zh', 'CN'),
|
||||||
|
delegates: GlobalMaterialLocalizations.delegates,
|
||||||
|
child: new LocalizationTracker(key: const ValueKey<String>('inner')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
final LocalizationTrackerState outerTracker = tester.state(find.byKey(const ValueKey<String>('outer')));
|
||||||
|
expect(outerTracker.captionFontSize, 12.0);
|
||||||
|
final LocalizationTrackerState innerTracker = tester.state(find.byKey(const ValueKey<String>('inner')));
|
||||||
|
expect(innerTracker.captionFontSize, 13.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Localizations is compatible with ChangeNotifier.dispose() called during didChangeDependencies', (WidgetTester tester) async {
|
||||||
|
// PageView calls ScrollPosition.dispose() during didChangeDependencies.
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MaterialApp(
|
||||||
|
supportedLocales: const <Locale>[
|
||||||
|
const Locale('en', 'US'),
|
||||||
|
const Locale('es', 'ES'),
|
||||||
|
],
|
||||||
|
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
|
||||||
|
new _DummyLocalizationsDelegate(),
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
],
|
||||||
|
home: new PageView(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.binding.setLocale('es', 'US');
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pumpWidget(new Container());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A localizations delegate that does not contain any useful data, and is only
|
||||||
|
/// used to trigger didChangeDependencies upon locale change.
|
||||||
|
class _DummyLocalizationsDelegate extends LocalizationsDelegate<DummyLocalizations> {
|
||||||
|
@override
|
||||||
|
Future<DummyLocalizations> load(Locale locale) async => new DummyLocalizations();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldReload(_DummyLocalizationsDelegate old) => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyLocalizations {}
|
||||||
|
|
||||||
|
class LocalizationTracker extends StatefulWidget {
|
||||||
|
LocalizationTracker({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => new LocalizationTrackerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalizationTrackerState extends State<LocalizationTracker> {
|
||||||
|
double captionFontSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
captionFontSize = Theme.of(context).textTheme.caption.fontSize;
|
||||||
|
return new Container();
|
||||||
|
}
|
||||||
|
}
|
236
packages/flutter_localizations/test/date_picker_test.dart
Normal file
236
packages/flutter_localizations/test/date_picker_test.dart
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
DateTime firstDate;
|
||||||
|
DateTime lastDate;
|
||||||
|
DateTime initialDate;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
firstDate = new DateTime(2001, DateTime.JANUARY, 1);
|
||||||
|
lastDate = new DateTime(2031, DateTime.DECEMBER, 31);
|
||||||
|
initialDate = new DateTime(2016, DateTime.JANUARY, 15);
|
||||||
|
});
|
||||||
|
|
||||||
|
group(DayPicker, () {
|
||||||
|
final Map<Locale, Map<String, dynamic>> testLocales = <Locale, Map<String, dynamic>>{
|
||||||
|
// Tests the default.
|
||||||
|
const Locale('en', 'US'): <String, dynamic>{
|
||||||
|
'textDirection': TextDirection.ltr,
|
||||||
|
'expectedDaysOfWeek': <String>['S', 'M', 'T', 'W', 'T', 'F', 'S'],
|
||||||
|
'expectedDaysOfMonth': new List<String>.generate(30, (int i) => '${i + 1}'),
|
||||||
|
'expectedMonthYearHeader': 'September 2017',
|
||||||
|
},
|
||||||
|
// Tests a different first day of week.
|
||||||
|
const Locale('ru', 'RU'): <String, dynamic>{
|
||||||
|
'textDirection': TextDirection.ltr,
|
||||||
|
'expectedDaysOfWeek': <String>['пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс'],
|
||||||
|
'expectedDaysOfMonth': new List<String>.generate(30, (int i) => '${i + 1}'),
|
||||||
|
'expectedMonthYearHeader': 'сентябрь 2017 г.',
|
||||||
|
},
|
||||||
|
// Tests RTL.
|
||||||
|
// TODO: change to Arabic numerals when these are fixed:
|
||||||
|
// TODO: https://github.com/dart-lang/intl/issues/143
|
||||||
|
// TODO: https://github.com/flutter/flutter/issues/12289
|
||||||
|
const Locale('ar', 'AR'): <String, dynamic>{
|
||||||
|
'textDirection': TextDirection.rtl,
|
||||||
|
'expectedDaysOfWeek': <String>['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'],
|
||||||
|
'expectedDaysOfMonth': new List<String>.generate(30, (int i) => '${i + 1}'),
|
||||||
|
'expectedMonthYearHeader': 'سبتمبر 2017',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (Locale locale in testLocales.keys) {
|
||||||
|
testWidgets('shows dates for $locale', (WidgetTester tester) async {
|
||||||
|
final List<String> expectedDaysOfWeek = testLocales[locale]['expectedDaysOfWeek'];
|
||||||
|
final List<String> expectedDaysOfMonth = testLocales[locale]['expectedDaysOfMonth'];
|
||||||
|
final String expectedMonthYearHeader = testLocales[locale]['expectedMonthYearHeader'];
|
||||||
|
final TextDirection textDirection = testLocales[locale]['textDirection'];
|
||||||
|
final DateTime baseDate = new DateTime(2017, 9, 27);
|
||||||
|
|
||||||
|
await _pumpBoilerplate(tester, new DayPicker(
|
||||||
|
selectedDate: baseDate,
|
||||||
|
currentDate: baseDate,
|
||||||
|
onChanged: (DateTime newValue) {},
|
||||||
|
firstDate: baseDate.subtract(const Duration(days: 90)),
|
||||||
|
lastDate: baseDate.add(const Duration(days: 90)),
|
||||||
|
displayedMonth: baseDate,
|
||||||
|
), locale: locale, textDirection: textDirection);
|
||||||
|
|
||||||
|
expect(find.text(expectedMonthYearHeader), findsOneWidget);
|
||||||
|
|
||||||
|
expectedDaysOfWeek.forEach((String dayOfWeek) {
|
||||||
|
expect(find.text(dayOfWeek), findsWidgets);
|
||||||
|
});
|
||||||
|
|
||||||
|
Offset previousCellOffset;
|
||||||
|
expectedDaysOfMonth.forEach((String dayOfMonth) {
|
||||||
|
final Finder dayCell = find.descendant(of: find.byType(GridView), matching: find.text(dayOfMonth));
|
||||||
|
expect(dayCell, findsOneWidget);
|
||||||
|
|
||||||
|
// Check that cells are correctly positioned relative to each other,
|
||||||
|
// taking text direction into account.
|
||||||
|
final Offset offset = tester.getCenter(dayCell);
|
||||||
|
if (previousCellOffset != null) {
|
||||||
|
if (textDirection == TextDirection.ltr) {
|
||||||
|
expect(offset.dx > previousCellOffset.dx && offset.dy == previousCellOffset.dy || offset.dy > previousCellOffset.dy, true);
|
||||||
|
} else {
|
||||||
|
expect(offset.dx < previousCellOffset.dx && offset.dy == previousCellOffset.dy || offset.dy > previousCellOffset.dy, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previousCellOffset = offset;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('locale parameter overrides ambient locale', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(new MaterialApp(
|
||||||
|
locale: const Locale('en', 'US'),
|
||||||
|
supportedLocales: const <Locale>[
|
||||||
|
const Locale('en', 'US'),
|
||||||
|
const Locale('fr', 'CA'),
|
||||||
|
],
|
||||||
|
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||||
|
home: new Material(
|
||||||
|
child: new Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return new FlatButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: initialDate,
|
||||||
|
firstDate: firstDate,
|
||||||
|
lastDate: lastDate,
|
||||||
|
locale: const Locale('fr', 'CA'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('X'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
await tester.tap(find.text('X'));
|
||||||
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
final Element dayPicker = tester.element(find.byType(DayPicker));
|
||||||
|
expect(
|
||||||
|
Localizations.localeOf(dayPicker),
|
||||||
|
const Locale('fr', 'CA'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
Directionality.of(dayPicker),
|
||||||
|
TextDirection.ltr,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.text('ANNULER'));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('textDirection parameter overrides ambient textDirection', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(new MaterialApp(
|
||||||
|
locale: const Locale('en', 'US'),
|
||||||
|
supportedLocales: const <Locale>[
|
||||||
|
const Locale('en', 'US'),
|
||||||
|
],
|
||||||
|
home: new Material(
|
||||||
|
child: new Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return new FlatButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: initialDate,
|
||||||
|
firstDate: firstDate,
|
||||||
|
lastDate: lastDate,
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('X'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
await tester.tap(find.text('X'));
|
||||||
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
final Element dayPicker = tester.element(find.byType(DayPicker));
|
||||||
|
expect(
|
||||||
|
Directionality.of(dayPicker),
|
||||||
|
TextDirection.rtl,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.text('CANCEL'));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('textDirection parameter takes precendence over locale parameter', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(new MaterialApp(
|
||||||
|
locale: const Locale('en', 'US'),
|
||||||
|
supportedLocales: const <Locale>[
|
||||||
|
const Locale('en', 'US'),
|
||||||
|
const Locale('fr', 'CA'),
|
||||||
|
],
|
||||||
|
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||||
|
home: new Material(
|
||||||
|
child: new Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return new FlatButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: initialDate,
|
||||||
|
firstDate: firstDate,
|
||||||
|
lastDate: lastDate,
|
||||||
|
locale: const Locale('fr', 'CA'),
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('X'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
await tester.tap(find.text('X'));
|
||||||
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
final Element dayPicker = tester.element(find.byType(DayPicker));
|
||||||
|
expect(
|
||||||
|
Localizations.localeOf(dayPicker),
|
||||||
|
const Locale('fr', 'CA'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
Directionality.of(dayPicker),
|
||||||
|
TextDirection.rtl,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.text('ANNULER'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Null> _pumpBoilerplate(
|
||||||
|
WidgetTester tester,
|
||||||
|
Widget child, {
|
||||||
|
Locale locale = const Locale('en', 'US'),
|
||||||
|
TextDirection textDirection: TextDirection.ltr
|
||||||
|
}) async {
|
||||||
|
await tester.pumpWidget(new Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: new Localizations(
|
||||||
|
locale: locale,
|
||||||
|
delegates: GlobalMaterialLocalizations.delegates,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
122
packages/flutter_localizations/test/date_time_test.dart
Normal file
122
packages/flutter_localizations/test/date_time_test.dart
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group(GlobalMaterialLocalizations, () {
|
||||||
|
test('uses exact locale when exists', () {
|
||||||
|
final GlobalMaterialLocalizations localizations = new GlobalMaterialLocalizations(const Locale('pt', 'PT'));
|
||||||
|
expect(localizations.formatDecimal(10000), '10\u00A0000');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('falls back to language code when exact locale is missing', () {
|
||||||
|
final GlobalMaterialLocalizations localizations = new GlobalMaterialLocalizations(const Locale('pt', 'XX'));
|
||||||
|
expect(localizations.formatDecimal(10000), '10.000');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('falls back to default format when neither language code nor exact locale are available', () {
|
||||||
|
final GlobalMaterialLocalizations localizations = new GlobalMaterialLocalizations(const Locale('xx', 'XX'));
|
||||||
|
expect(localizations.formatDecimal(10000), '10,000');
|
||||||
|
});
|
||||||
|
|
||||||
|
group('formatHour', () {
|
||||||
|
test('formats h', () {
|
||||||
|
GlobalMaterialLocalizations localizations;
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('en', 'US'));
|
||||||
|
expect(localizations.formatHour(const TimeOfDay(hour: 10, minute: 0)), '10');
|
||||||
|
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '8');
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('ar', ''));
|
||||||
|
expect(localizations.formatHour(const TimeOfDay(hour: 10, minute: 0)), '١٠');
|
||||||
|
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '٨');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formats HH', () {
|
||||||
|
GlobalMaterialLocalizations localizations;
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('de', ''));
|
||||||
|
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '09');
|
||||||
|
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('en', 'GB'));
|
||||||
|
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '09');
|
||||||
|
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formats H', () {
|
||||||
|
GlobalMaterialLocalizations localizations;
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('es', ''));
|
||||||
|
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '9');
|
||||||
|
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('fa', ''));
|
||||||
|
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '۹');
|
||||||
|
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '۲۰');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('formatMinute', () {
|
||||||
|
test('formats English', () {
|
||||||
|
final GlobalMaterialLocalizations localizations = new GlobalMaterialLocalizations(const Locale('en', 'US'));
|
||||||
|
expect(localizations.formatMinute(const TimeOfDay(hour: 1, minute: 32)), '32');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formats Arabic', () {
|
||||||
|
final GlobalMaterialLocalizations localizations = new GlobalMaterialLocalizations(const Locale('ar', ''));
|
||||||
|
expect(localizations.formatMinute(const TimeOfDay(hour: 1, minute: 32)), '٣٢');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('formatTimeOfDay', () {
|
||||||
|
test('formats ${TimeOfDayFormat.h_colon_mm_space_a}', () {
|
||||||
|
GlobalMaterialLocalizations localizations;
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('ar', ''));
|
||||||
|
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '٩:٣٢ ص');
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('en', ''));
|
||||||
|
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32 AM');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formats ${TimeOfDayFormat.HH_colon_mm}', () {
|
||||||
|
GlobalMaterialLocalizations localizations;
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('de', ''));
|
||||||
|
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09:32');
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('en', 'ZA'));
|
||||||
|
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09:32');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formats ${TimeOfDayFormat.H_colon_mm}', () {
|
||||||
|
GlobalMaterialLocalizations localizations;
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('es', ''));
|
||||||
|
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32');
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('ja', ''));
|
||||||
|
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formats ${TimeOfDayFormat.frenchCanadian}', () {
|
||||||
|
GlobalMaterialLocalizations localizations;
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('fr', 'CA'));
|
||||||
|
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09 h 32');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formats ${TimeOfDayFormat.a_space_h_colon_mm}', () {
|
||||||
|
GlobalMaterialLocalizations localizations;
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('zh', ''));
|
||||||
|
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '上午 9:32');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
214
packages/flutter_localizations/test/override_test.dart
Normal file
214
packages/flutter_localizations/test/override_test.dart
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
class FooMaterialLocalizations extends GlobalMaterialLocalizations {
|
||||||
|
FooMaterialLocalizations(Locale locale) : super(locale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backButtonTooltip => 'foo';
|
||||||
|
}
|
||||||
|
|
||||||
|
class FooMaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
|
||||||
|
const FooMaterialLocalizationsDelegate();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<FooMaterialLocalizations> load(Locale locale) {
|
||||||
|
return new SynchronousFuture<FooMaterialLocalizations>(new FooMaterialLocalizations(locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldReload(FooMaterialLocalizationsDelegate old) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildFrame({
|
||||||
|
Locale locale,
|
||||||
|
Iterable<LocalizationsDelegate<dynamic>> delegates: GlobalMaterialLocalizations.delegates,
|
||||||
|
WidgetBuilder buildContent,
|
||||||
|
LocaleResolutionCallback localeResolutionCallback,
|
||||||
|
Iterable<Locale> supportedLocales: const <Locale>[
|
||||||
|
const Locale('en', 'US'),
|
||||||
|
const Locale('es', 'es'),
|
||||||
|
],
|
||||||
|
}) {
|
||||||
|
return new MaterialApp(
|
||||||
|
color: const Color(0xFFFFFFFF),
|
||||||
|
locale: locale,
|
||||||
|
supportedLocales: supportedLocales,
|
||||||
|
localizationsDelegates: delegates,
|
||||||
|
localeResolutionCallback: localeResolutionCallback,
|
||||||
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
|
return new MaterialPageRoute<Null>(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return buildContent(context);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Locale fallbacks', (WidgetTester tester) async {
|
||||||
|
final Key textKey = new UniqueKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
buildFrame(
|
||||||
|
buildContent: (BuildContext context) {
|
||||||
|
return new Text(
|
||||||
|
MaterialLocalizations.of(context).backButtonTooltip,
|
||||||
|
key: textKey,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(tester.widget<Text>(find.byKey(textKey)).data, 'Back');
|
||||||
|
|
||||||
|
// Unrecognized locale falls back to 'en'
|
||||||
|
await tester.binding.setLocale('foo', 'bar');
|
||||||
|
await tester.pump();
|
||||||
|
expect(tester.widget<Text>(find.byKey(textKey)).data, 'Back');
|
||||||
|
|
||||||
|
// Spanish Bolivia locale, falls back to just 'es'
|
||||||
|
await tester.binding.setLocale('es', 'bo');
|
||||||
|
await tester.pump();
|
||||||
|
expect(tester.widget<Text>(find.byKey(textKey)).data, 'Espalda');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Localizations.override widget tracks parent\'s locale', (WidgetTester tester) async {
|
||||||
|
Widget buildLocaleFrame(Locale locale) {
|
||||||
|
return buildFrame(
|
||||||
|
locale: locale,
|
||||||
|
buildContent: (BuildContext context) {
|
||||||
|
return new Localizations.override(
|
||||||
|
context: context,
|
||||||
|
child: new Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
// No MaterialLocalizations are defined for the first Localizations
|
||||||
|
// ancestor, so we should get the values from the default one, i.e.
|
||||||
|
// the one created by WidgetsApp via the LocalizationsDelegate
|
||||||
|
// provided by MaterialApp.
|
||||||
|
return new Text(MaterialLocalizations.of(context).backButtonTooltip);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US')));
|
||||||
|
expect(find.text('Back'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildLocaleFrame(const Locale('de', 'DE')));
|
||||||
|
expect(find.text('Zurück'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildLocaleFrame(const Locale('zh', 'CN')));
|
||||||
|
expect(find.text('返回'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Localizations.override widget with hardwired locale', (WidgetTester tester) async {
|
||||||
|
Widget buildLocaleFrame(Locale locale) {
|
||||||
|
return buildFrame(
|
||||||
|
locale: locale,
|
||||||
|
buildContent: (BuildContext context) {
|
||||||
|
return new Localizations.override(
|
||||||
|
context: context,
|
||||||
|
locale: const Locale('en', 'US'),
|
||||||
|
child: new Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
// No MaterialLocalizations are defined for the Localizations.override
|
||||||
|
// ancestor, so we should get all values from the default one, i.e.
|
||||||
|
// the one created by WidgetsApp via the LocalizationsDelegate
|
||||||
|
// provided by MaterialApp.
|
||||||
|
return new Text(MaterialLocalizations.of(context).backButtonTooltip);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US')));
|
||||||
|
expect(find.text('Back'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildLocaleFrame(const Locale('de', 'DE')));
|
||||||
|
expect(find.text('Back'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildLocaleFrame(const Locale('zh', 'CN')));
|
||||||
|
expect(find.text('Back'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('MaterialApp overrides MaterialLocalizations', (WidgetTester tester) async {
|
||||||
|
final Key textKey = new UniqueKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
buildFrame(
|
||||||
|
// Accept whatever locale we're given
|
||||||
|
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
|
||||||
|
delegates: <FooMaterialLocalizationsDelegate>[
|
||||||
|
const FooMaterialLocalizationsDelegate(),
|
||||||
|
],
|
||||||
|
buildContent: (BuildContext context) {
|
||||||
|
// Should always be 'foo', no matter what the locale is
|
||||||
|
return new Text(
|
||||||
|
MaterialLocalizations.of(context).backButtonTooltip,
|
||||||
|
key: textKey,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(tester.widget<Text>(find.byKey(textKey)).data, 'foo');
|
||||||
|
|
||||||
|
await tester.binding.setLocale('zh', 'CN');
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text('foo'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.binding.setLocale('de', 'DE');
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text('foo'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('deprecated Android/Java locales are modernized', (WidgetTester tester) async {
|
||||||
|
final Key textKey = new UniqueKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
buildFrame(
|
||||||
|
supportedLocales: <Locale>[
|
||||||
|
const Locale('en', 'US'),
|
||||||
|
const Locale('he', 'IL'),
|
||||||
|
const Locale('yi', 'IL'),
|
||||||
|
const Locale('id', 'JV'),
|
||||||
|
],
|
||||||
|
buildContent: (BuildContext context) {
|
||||||
|
return new Text(
|
||||||
|
'${Localizations.localeOf(context)}',
|
||||||
|
key: textKey,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(tester.widget<Text>(find.byKey(textKey)).data, 'en_US');
|
||||||
|
|
||||||
|
// Hebrew was iw (ISO-639) is he (ISO-639-1)
|
||||||
|
await tester.binding.setLocale('iw', 'IL');
|
||||||
|
await tester.pump();
|
||||||
|
expect(tester.widget<Text>(find.byKey(textKey)).data, 'he_IL');
|
||||||
|
|
||||||
|
// Yiddish was ji (ISO-639) is yi (ISO-639-1)
|
||||||
|
await tester.binding.setLocale('ji', 'IL');
|
||||||
|
await tester.pump();
|
||||||
|
expect(tester.widget<Text>(find.byKey(textKey)).data, 'yi_IL');
|
||||||
|
|
||||||
|
// Indonesian was in (ISO-639) is id (ISO-639-1)
|
||||||
|
await tester.binding.setLocale('in', 'JV');
|
||||||
|
await tester.pump();
|
||||||
|
expect(tester.widget<Text>(find.byKey(textKey)).data, 'id_JV');
|
||||||
|
});
|
||||||
|
}
|
129
packages/flutter_localizations/test/time_picker_test.dart
Normal file
129
packages/flutter_localizations/test/time_picker_test.dart
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
class _TimePickerLauncher extends StatelessWidget {
|
||||||
|
const _TimePickerLauncher({ Key key, this.onChanged, this.locale }) : super(key: key);
|
||||||
|
|
||||||
|
final ValueChanged<TimeOfDay> onChanged;
|
||||||
|
final Locale locale;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new MaterialApp(
|
||||||
|
locale: locale,
|
||||||
|
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||||
|
home: new Material(
|
||||||
|
child: new Center(
|
||||||
|
child: new Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return new RaisedButton(
|
||||||
|
child: const Text('X'),
|
||||||
|
onPressed: () async {
|
||||||
|
onChanged(await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: const TimeOfDay(hour: 7, minute: 0)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Offset> startPicker(WidgetTester tester, ValueChanged<TimeOfDay> onChanged,
|
||||||
|
{ Locale locale: const Locale('en', 'US') }) async {
|
||||||
|
await tester.pumpWidget(new _TimePickerLauncher(onChanged: onChanged, locale: locale,));
|
||||||
|
await tester.tap(find.text('X'));
|
||||||
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||||
|
return tester.getCenter(find.byKey(const Key('time-picker-dial')));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Null> finishPicker(WidgetTester tester) async {
|
||||||
|
final Element timePickerElement = tester.element(find.byElementPredicate((Element element) => element.widget.runtimeType.toString() == '_TimePickerDialog'));
|
||||||
|
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(timePickerElement);
|
||||||
|
await tester.tap(find.text(materialLocalizations.okButtonLabel));
|
||||||
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('can localize the header in all known formats', (WidgetTester tester) async {
|
||||||
|
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
|
||||||
|
final Map<Locale, List<String>> locales = <Locale, List<String>>{
|
||||||
|
const Locale('en', 'US'): const <String>['hour h', 'string :', 'minute', 'period'], //'h:mm a'
|
||||||
|
const Locale('en', 'GB'): const <String>['hour HH', 'string :', 'minute'], //'HH:mm'
|
||||||
|
const Locale('es', 'ES'): const <String>['hour H', 'string :', 'minute'], //'H:mm'
|
||||||
|
const Locale('fr', 'CA'): const <String>['hour HH', 'string h', 'minute'], //'HH \'h\' mm'
|
||||||
|
const Locale('zh', 'ZH'): const <String>['period', 'hour h', 'string :', 'minute'], //'ah:mm'
|
||||||
|
};
|
||||||
|
|
||||||
|
for (Locale locale in locales.keys) {
|
||||||
|
final Offset center = await startPicker(tester, (TimeOfDay time) { }, locale: locale);
|
||||||
|
final List<String> actual = <String>[];
|
||||||
|
tester.element(find.byType(CustomMultiChildLayout)).visitChildren((Element child) {
|
||||||
|
final LayoutId layout = child.widget;
|
||||||
|
final String fragmentType = '${layout.child.runtimeType}';
|
||||||
|
final dynamic widget = layout.child;
|
||||||
|
if (fragmentType == '_MinuteControl') {
|
||||||
|
actual.add('minute');
|
||||||
|
} else if (fragmentType == '_DayPeriodControl') {
|
||||||
|
actual.add('period');
|
||||||
|
} else if (fragmentType == '_HourControl') {
|
||||||
|
actual.add('hour ${widget.hourFormat.toString().split('.').last}');
|
||||||
|
} else if (fragmentType == '_StringFragment') {
|
||||||
|
actual.add('string ${widget.value}');
|
||||||
|
} else {
|
||||||
|
fail('Unsupported fragment type: $fragmentType');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(actual, locales[locale]);
|
||||||
|
await tester.tapAt(new Offset(center.dx, center.dy - 50.0));
|
||||||
|
await finishPicker(tester);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('uses single-ring 12-hour dial for h hour format', (WidgetTester tester) async {
|
||||||
|
// Tap along the segment stretching from the center to the edge at
|
||||||
|
// 12:00 AM position. Because there's only one ring, no matter where you
|
||||||
|
// tap the time will be the same. See the 24-hour dial test that behaves
|
||||||
|
// differently.
|
||||||
|
for (int i = 1; i < 10; i++) {
|
||||||
|
TimeOfDay result;
|
||||||
|
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
||||||
|
final Size size = tester.getSize(find.byKey(const Key('time-picker-dial')));
|
||||||
|
final double dy = (size.height / 2.0 / 10) * i;
|
||||||
|
await tester.tapAt(new Offset(center.dx, center.dy - dy));
|
||||||
|
await finishPicker(tester);
|
||||||
|
expect(result, equals(const TimeOfDay(hour: 0, minute: 0)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('uses two-ring 24-hour dial for H and HH hour formats', (WidgetTester tester) async {
|
||||||
|
const List<Locale> locales = const <Locale>[
|
||||||
|
const Locale('en', 'GB'), // HH
|
||||||
|
const Locale('es', 'ES'), // H
|
||||||
|
];
|
||||||
|
for (Locale locale in locales) {
|
||||||
|
// Tap along the segment stretching from the center to the edge at
|
||||||
|
// 12:00 AM position. There are two rings. At ~70% mark, the ring
|
||||||
|
// switches between inner ring and outer ring.
|
||||||
|
for (int i = 1; i < 10; i++) {
|
||||||
|
TimeOfDay result;
|
||||||
|
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; }, locale: locale);
|
||||||
|
final Size size = tester.getSize(find.byKey(const Key('time-picker-dial')));
|
||||||
|
final double dy = (size.height / 2.0 / 10) * i;
|
||||||
|
await tester.tapAt(new Offset(center.dx, center.dy - dy));
|
||||||
|
await finishPicker(tester);
|
||||||
|
expect(result, equals(new TimeOfDay(hour: i < 7 ? 12 : 0, minute: 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
89
packages/flutter_localizations/test/translations_test.dart
Normal file
89
packages/flutter_localizations/test/translations_test.dart
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final List<String> languages = <String>[
|
||||||
|
'ar', // Arabic
|
||||||
|
'de', // German
|
||||||
|
'en', // English
|
||||||
|
'es', // Spanish
|
||||||
|
'fa', // Farsi (Persian)
|
||||||
|
'fr', // French
|
||||||
|
'he', // Hebrew
|
||||||
|
'it', // Italian
|
||||||
|
'ja', // Japanese
|
||||||
|
'ps', // Pashto
|
||||||
|
'pt', // Portugese
|
||||||
|
'ru', // Russian
|
||||||
|
'sd', // Sindhi
|
||||||
|
'ur', // Urdu
|
||||||
|
'zh', // Chinese (simplified)
|
||||||
|
];
|
||||||
|
|
||||||
|
for (String language in languages) {
|
||||||
|
testWidgets('translations exist for $language', (WidgetTester tester) async {
|
||||||
|
final Locale locale = new Locale(language, '');
|
||||||
|
final MaterialLocalizations localizations = new GlobalMaterialLocalizations(locale);
|
||||||
|
|
||||||
|
expect(localizations.openAppDrawerTooltip, isNotNull);
|
||||||
|
expect(localizations.backButtonTooltip, isNotNull);
|
||||||
|
expect(localizations.closeButtonTooltip, isNotNull);
|
||||||
|
expect(localizations.nextMonthTooltip, isNotNull);
|
||||||
|
expect(localizations.previousMonthTooltip, isNotNull);
|
||||||
|
expect(localizations.nextPageTooltip, isNotNull);
|
||||||
|
expect(localizations.previousPageTooltip, isNotNull);
|
||||||
|
expect(localizations.showMenuTooltip, isNotNull);
|
||||||
|
expect(localizations.licensesPageTitle, isNotNull);
|
||||||
|
expect(localizations.rowsPerPageTitle, isNotNull);
|
||||||
|
expect(localizations.cancelButtonLabel, isNotNull);
|
||||||
|
expect(localizations.closeButtonLabel, isNotNull);
|
||||||
|
expect(localizations.continueButtonLabel, isNotNull);
|
||||||
|
expect(localizations.copyButtonLabel, isNotNull);
|
||||||
|
expect(localizations.cutButtonLabel, isNotNull);
|
||||||
|
expect(localizations.okButtonLabel, isNotNull);
|
||||||
|
expect(localizations.pasteButtonLabel, isNotNull);
|
||||||
|
expect(localizations.selectAllButtonLabel, isNotNull);
|
||||||
|
expect(localizations.viewLicensesButtonLabel, isNotNull);
|
||||||
|
|
||||||
|
expect(localizations.aboutListTileTitle('FOO'), isNotNull);
|
||||||
|
expect(localizations.aboutListTileTitle('FOO'), contains('FOO'));
|
||||||
|
|
||||||
|
expect(localizations.selectedRowCountTitle(0), isNotNull);
|
||||||
|
expect(localizations.selectedRowCountTitle(1), isNotNull);
|
||||||
|
expect(localizations.selectedRowCountTitle(2), isNotNull);
|
||||||
|
expect(localizations.selectedRowCountTitle(100), isNotNull);
|
||||||
|
expect(localizations.selectedRowCountTitle(0).contains(r'$selectedRowCount'), isFalse);
|
||||||
|
expect(localizations.selectedRowCountTitle(1).contains(r'$selectedRowCount'), isFalse);
|
||||||
|
expect(localizations.selectedRowCountTitle(2).contains(r'$selectedRowCount'), isFalse);
|
||||||
|
expect(localizations.selectedRowCountTitle(100).contains(r'$selectedRowCount'), isFalse);
|
||||||
|
|
||||||
|
expect(localizations.pageRowsInfoTitle(1, 10, 100, true), isNotNull);
|
||||||
|
expect(localizations.pageRowsInfoTitle(1, 10, 100, false), isNotNull);
|
||||||
|
expect(localizations.pageRowsInfoTitle(1, 10, 100, true).contains(r'$firstRow'), isFalse);
|
||||||
|
expect(localizations.pageRowsInfoTitle(1, 10, 100, true).contains(r'$lastRow'), isFalse);
|
||||||
|
expect(localizations.pageRowsInfoTitle(1, 10, 100, true).contains(r'$rowCount'), isFalse);
|
||||||
|
expect(localizations.pageRowsInfoTitle(1, 10, 100, false).contains(r'$firstRow'), isFalse);
|
||||||
|
expect(localizations.pageRowsInfoTitle(1, 10, 100, false).contains(r'$lastRow'), isFalse);
|
||||||
|
expect(localizations.pageRowsInfoTitle(1, 10, 100, false).contains(r'$rowCount'), isFalse);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('spot check selectedRowCount translations', (WidgetTester tester) async {
|
||||||
|
MaterialLocalizations localizations = new GlobalMaterialLocalizations(const Locale('en', ''));
|
||||||
|
expect(localizations.selectedRowCountTitle(0), 'No items selected');
|
||||||
|
expect(localizations.selectedRowCountTitle(1), '1 item selected');
|
||||||
|
expect(localizations.selectedRowCountTitle(2), '2 items selected');
|
||||||
|
expect(localizations.selectedRowCountTitle(123456789), '123,456,789 items selected');
|
||||||
|
|
||||||
|
localizations = new GlobalMaterialLocalizations(const Locale('es', ''));
|
||||||
|
expect(localizations.selectedRowCountTitle(0), 'No se han seleccionado elementos');
|
||||||
|
expect(localizations.selectedRowCountTitle(1), '1 artículo seleccionado');
|
||||||
|
expect(localizations.selectedRowCountTitle(2), '2 artículos seleccionados');
|
||||||
|
expect(localizations.selectedRowCountTitle(123456789), '123.456.789 artículos seleccionados');
|
||||||
|
});
|
||||||
|
}
|
@ -5,6 +5,7 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_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,
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user