diff --git a/dev/tools/gen_localizations.dart b/dev/tools/gen_localizations.dart new file mode 100644 index 00000000000..0a7a8389612 --- /dev/null +++ b/dev/tools/gen_localizations.dart @@ -0,0 +1,127 @@ +// 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. + +// Given a directory that contains localized ".arb" (application resource +// bundle) files, generates a Dart "localizations" Map definition that combines +// the contents of the arb files. The map can be used to lookup a localized +// string: localizations[localeString][resourceId]. +// +// See *.arb and localizations.dart in packages/flutter/lib/src/material/i18n/. +// +// The arb (JSON) format files must contain a single map indexed by locale. +// Each map value is itself a map with resource identifier keys and localized +// resource string values. +// +// The arb filenames are assumed to end in "prefix_lc.arb" or "prefix_lc_cc.arb", +// where prefix is the 2nd command line argument, lc is a language code and cc +// is the country code. In most cases both codes are just two characters. A typical +// filename would be "material_en.arb". +// +// This app is typically run by hand when a module's .arb files have been +// updated. +// +// Usage: dart gen_localizations.dart directory prefix + +import 'dart:convert' show JSON; +import 'dart:io'; + +const String outputHeader = ''' +// 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. + +// This file has been automatically generated. Please do not edit it manually. +// To regenerate the file, use: +// @(regenerate) +'''; + +final Map> localeToResources = >{}; + +// Return s as a Dart-parseable raw string in double quotes. Expand double quotes: +// foo => r"foo" +// foo "bar" => r"foo " '"' r"bar" '"' +String generateString(String s) { + if (!s.contains('"')) + return 'r"$s"'; + + final StringBuffer output = new StringBuffer(); + bool started = false; // Have we started writing a raw string. + for (int i = 0; i < s.length; i++) { + if (s[i] == '"') { + if (started) + output.write('"'); + output.write(' \'"\' '); + started = false; + } else if (!started) { + output.write('r"${s[i]}'); + started = true; + } else { + output.write(s[i]); + } + } + if (started) + output.write('"'); + return output.toString(); +} + +String generateLocalizationsMap() { + final StringBuffer output = new StringBuffer(); + + output.writeln('const Map> localizations = const > {'); + + final String lastLocale = localeToResources.keys.last; + for (String locale in localeToResources.keys) { + output.writeln(' "$locale": const {'); + + final Map resources = localeToResources[locale]; + final String lastName = resources.keys.last; + for (String name in resources.keys) { + final String comma = name == lastName ? "" : ","; + final String value = generateString(resources[name]); + output.writeln(' "$name": $value$comma'); + } + final String comma = locale == lastLocale ? "" : ","; + output.writeln(' }$comma'); + } + + output.writeln('};'); + return output.toString(); +} + +void processBundle(File file, String locale) { + localeToResources[locale] ??= {}; + final Map resources = localeToResources[locale]; + final Map bundle = JSON.decode(file.readAsStringSync()); + for (String key in bundle.keys) { + // The ARB file resource "attributes" for foo are called @foo. + if (key.startsWith('@')) + continue; + resources[key] = bundle[key]; + } +} + +void main(List args) { + if (args.length != 2) + stderr.writeln('Usage: dart gen_localizations.dart directory prefix'); + + // filenames are assumed to end in "prefix_lc.arb" or "prefix_lc_cc.arb", where prefix + // is the 2nd command line argument, lc is a language code and cc is the country + // code. In most cases both codes are just two characters. + + final Directory directory = new Directory(args[0]); + final String prefix = args[1]; + final RegExp filenameRE = new RegExp('${prefix}_(\\w+)\\.arb\$'); + + for (FileSystemEntity entity in directory.listSync()) { + final String path = entity.path; + if (FileSystemEntity.isFileSync(path) && filenameRE.hasMatch(path)) { + final String locale = filenameRE.firstMatch(path)[1]; + processBundle(new File(path), locale); + } + } + + final String regenerate = 'dart gen_localizations ${directory.path} ${args[1]}'; + print(outputHeader.replaceFirst('@(regenerate)', regenerate)); + print(generateLocalizationsMap()); +} diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart index a11462219a2..4d063603ddd 100644 --- a/packages/flutter/lib/src/material/app.dart +++ b/packages/flutter/lib/src/material/app.dart @@ -26,19 +26,17 @@ const TextStyle _errorTextStyle = const TextStyle( decorationStyle: TextDecorationStyle.double ); -// Delegate that fetches the default (English) strings. class _MaterialLocalizationsDelegate extends LocalizationsDelegate { const _MaterialLocalizationsDelegate(); @override - Future load(Locale locale) { - return new SynchronousFuture(const MaterialLocalizations()); - } + Future load(Locale locale) => DefaultMaterialLocalizations.load(locale); @override bool shouldReload(_MaterialLocalizationsDelegate old) => false; } + /// An application that uses material design. /// /// A convenience widget that wraps a number of widgets that are commonly diff --git a/packages/flutter/lib/src/material/i18n/localizations.dart b/packages/flutter/lib/src/material/i18n/localizations.dart new file mode 100644 index 00000000000..1808dbc9d27 --- /dev/null +++ b/packages/flutter/lib/src/material/i18n/localizations.dart @@ -0,0 +1,80 @@ +// 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. + +// This file has been automatically generated. Please do not edit it manually. +// To regenerate the file, use: +// dart gen_localizations packages/flutter/lib/src/material/i18n material + +const Map> localizations = const > { + "ar": const { + "openAppDrawerTooltip": r"افتح قائمة التنقل", + "backButtonTooltip": r"الى الخلف", + "closeButtonTooltip": r"إغلا", + "nextMonthTooltip": r"الشهر القادم", + "previousMonthTooltip": r"الشهر الماضى" + }, + "it": const { + "openAppDrawerTooltip": r"Apri il menu di navigazione", + "backButtonTooltip": r"Indietro", + "closeButtonTooltip": r"Chiudi", + "nextMonthTooltip": r"Il prossimo mese", + "previousMonthTooltip": r"Il mese scorso" + }, + "pt": const { + "openAppDrawerTooltip": r"Abrir menu de navegação", + "backButtonTooltip": r"Costas", + "closeButtonTooltip": r"Fechar", + "nextMonthTooltip": r"Próximo mês", + "previousMonthTooltip": r"Mês anterior" + }, + "es": const { + "openAppDrawerTooltip": r"Abrir el menú de navegación", + "backButtonTooltip": r"Espalda", + "closeButtonTooltip": r"Cerrar", + "nextMonthTooltip": r"Próximo mes", + "previousMonthTooltip": r"mes anterior" + }, + "fr": const { + "openAppDrawerTooltip": r"Ouvrir le menu de navigation", + "backButtonTooltip": r"Arrière", + "closeButtonTooltip": r"Fermer", + "nextMonthTooltip": r"Mois Suivant", + "previousMonthTooltip": r"Le mois précédent" + }, + "zh": const { + "openAppDrawerTooltip": r"打开导航菜单", + "backButtonTooltip": r"背部", + "closeButtonTooltip": r"关", + "nextMonthTooltip": r"-下月就29了。", + "previousMonthTooltip": r"前一个月" + }, + "en": const { + "openAppDrawerTooltip": r"Open navigation menu", + "backButtonTooltip": r"Back", + "closeButtonTooltip": r"Close", + "nextMonthTooltip": r"Next month", + "previousMonthTooltip": r"Previous month" + }, + "de": const { + "openAppDrawerTooltip": r"Navigationsmenü öffnen", + "backButtonTooltip": r"Zurück", + "closeButtonTooltip": r"Schließen ", + "nextMonthTooltip": r"Nächster Monat", + "previousMonthTooltip": r"Letzter Monat" + }, + "ja": const { + "openAppDrawerTooltip": r"ナビゲーションメニューを開く", + "backButtonTooltip": r"バック", + "closeButtonTooltip": r"閉じる", + "nextMonthTooltip": r"来月", + "previousMonthTooltip": r"前の月" + }, + "ru": const { + "openAppDrawerTooltip": r"Открыть меню навигации", + "backButtonTooltip": r"назад", + "closeButtonTooltip": r"Закрыть", + "nextMonthTooltip": r"В следующем месяце", + "previousMonthTooltip": r"Предыдущий месяц" + } +}; diff --git a/packages/flutter/lib/src/material/i18n/material_ar.arb b/packages/flutter/lib/src/material/i18n/material_ar.arb new file mode 100644 index 00000000000..16745bf1c64 --- /dev/null +++ b/packages/flutter/lib/src/material/i18n/material_ar.arb @@ -0,0 +1,27 @@ +{ + "openAppDrawerTooltip": "افتح قائمة التنقل", + "@openAppDrawerTooltip": { + "description": "The tooltip for the leading AppBar menu (aka 'hamburger') button", + "type": "text" + }, + "backButtonTooltip": "الى الخلف", + "@backButtonTooltip": { + "description": "The BackButton's tooltip", + "type": "text" + }, + "closeButtonTooltip": "إغلا", + "@closeButtonTooltip": { + "description": "The CloseButton's tooltip", + "type": "text" + }, + "nextMonthTooltip": "الشهر القادم", + "@nextMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'next month' button.", + "type": "text" + }, + "previousMonthTooltip": "الشهر الماضى", + "@previousMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'previous month' button.", + "type": "text" + } +} diff --git a/packages/flutter/lib/src/material/i18n/material_de.arb b/packages/flutter/lib/src/material/i18n/material_de.arb new file mode 100644 index 00000000000..e36bec6a370 --- /dev/null +++ b/packages/flutter/lib/src/material/i18n/material_de.arb @@ -0,0 +1,27 @@ +{ + "openAppDrawerTooltip": "Navigationsmenü öffnen", + "@openAppDrawerTooltip": { + "description": "The tooltip for the leading AppBar menu (aka 'hamburger') button", + "type": "text" + }, + "backButtonTooltip": "Zurück", + "@backButtonTooltip": { + "description": "The BackButton's tooltip", + "type": "text" + }, + "closeButtonTooltip": "Schließen ", + "@closeButtonTooltip": { + "description": "The CloseButton's tooltip", + "type": "text" + }, + "nextMonthTooltip": "Nächster Monat", + "@nextMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'next month' button.", + "type": "text" + }, + "previousMonthTooltip": "Letzter Monat", + "@previousMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'previous month' button.", + "type": "text" + } +} diff --git a/packages/flutter/lib/src/material/i18n/material_en.arb b/packages/flutter/lib/src/material/i18n/material_en.arb new file mode 100644 index 00000000000..784ad6a609e --- /dev/null +++ b/packages/flutter/lib/src/material/i18n/material_en.arb @@ -0,0 +1,31 @@ +{ + "openAppDrawerTooltip": "Open navigation menu", + "@openAppDrawerTooltip": { + "description": "The tooltip for the leading AppBar menu (aka 'hamburger') button", + "type": "text" + }, + + "backButtonTooltip": "Back", + "@backButtonTooltip": { + "description": "The BackButton's tooltip", + "type": "text" + }, + + "closeButtonTooltip": "Close", + "@closeButtonTooltip": { + "description": "The CloseButton's tooltip", + "type": "text" + }, + + "nextMonthTooltip": "Next month", + "@nextMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'next month' button.", + "type": "text" + }, + + "previousMonthTooltip": "Previous month", + "@previousMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'previous month' button.", + "type": "text" + } +} diff --git a/packages/flutter/lib/src/material/i18n/material_es.arb b/packages/flutter/lib/src/material/i18n/material_es.arb new file mode 100644 index 00000000000..ddb30530423 --- /dev/null +++ b/packages/flutter/lib/src/material/i18n/material_es.arb @@ -0,0 +1,27 @@ +{ + "openAppDrawerTooltip": "Abrir el menú de navegación", + "@openAppDrawerTooltip": { + "description": "The tooltip for the leading AppBar menu (aka 'hamburger') button", + "type": "text" + }, + "backButtonTooltip": "Espalda", + "@backButtonTooltip": { + "description": "The BackButton's tooltip", + "type": "text" + }, + "closeButtonTooltip": "Cerrar", + "@closeButtonTooltip": { + "description": "The CloseButton's tooltip", + "type": "text" + }, + "nextMonthTooltip": "Próximo mes", + "@nextMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'next month' button.", + "type": "text" + }, + "previousMonthTooltip": "mes anterior", + "@previousMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'previous month' button.", + "type": "text" + } +} diff --git a/packages/flutter/lib/src/material/i18n/material_fr.arb b/packages/flutter/lib/src/material/i18n/material_fr.arb new file mode 100644 index 00000000000..4ff56706493 --- /dev/null +++ b/packages/flutter/lib/src/material/i18n/material_fr.arb @@ -0,0 +1,27 @@ +{ + "openAppDrawerTooltip": "Ouvrir le menu de navigation", + "@openAppDrawerTooltip": { + "description": "The tooltip for the leading AppBar menu (aka 'hamburger') button", + "type": "text" + }, + "backButtonTooltip": "Arrière", + "@backButtonTooltip": { + "description": "The BackButton's tooltip", + "type": "text" + }, + "closeButtonTooltip": "Fermer", + "@closeButtonTooltip": { + "description": "The CloseButton's tooltip", + "type": "text" + }, + "nextMonthTooltip": "Mois Suivant", + "@nextMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'next month' button.", + "type": "text" + }, + "previousMonthTooltip": "Le mois précédent", + "@previousMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'previous month' button.", + "type": "text" + } +} diff --git a/packages/flutter/lib/src/material/i18n/material_it.arb b/packages/flutter/lib/src/material/i18n/material_it.arb new file mode 100644 index 00000000000..80030eaf53b --- /dev/null +++ b/packages/flutter/lib/src/material/i18n/material_it.arb @@ -0,0 +1,27 @@ +{ + "openAppDrawerTooltip": "Apri il menu di navigazione", + "@openAppDrawerTooltip": { + "description": "The tooltip for the leading AppBar menu (aka 'hamburger') button", + "type": "text" + }, + "backButtonTooltip": "Indietro", + "@backButtonTooltip": { + "description": "The BackButton's tooltip", + "type": "text" + }, + "closeButtonTooltip": "Chiudi", + "@closeButtonTooltip": { + "description": "The CloseButton's tooltip", + "type": "text" + }, + "nextMonthTooltip": "Il prossimo mese", + "@nextMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'next month' button.", + "type": "text" + }, + "previousMonthTooltip": "Il mese scorso", + "@previousMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'previous month' button.", + "type": "text" + } +} diff --git a/packages/flutter/lib/src/material/i18n/material_ja.arb b/packages/flutter/lib/src/material/i18n/material_ja.arb new file mode 100644 index 00000000000..4205c645c2e --- /dev/null +++ b/packages/flutter/lib/src/material/i18n/material_ja.arb @@ -0,0 +1,27 @@ +{ + "openAppDrawerTooltip": "ナビゲーションメニューを開く", + "@openAppDrawerTooltip": { + "description": "The tooltip for the leading AppBar menu (aka 'hamburger') button", + "type": "text" + }, + "backButtonTooltip": "バック", + "@backButtonTooltip": { + "description": "The BackButton's tooltip", + "type": "text" + }, + "closeButtonTooltip": "閉じる", + "@closeButtonTooltip": { + "description": "The CloseButton's tooltip", + "type": "text" + }, + "nextMonthTooltip": "来月", + "@nextMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'next month' button.", + "type": "text" + }, + "previousMonthTooltip": "前の月", + "@previousMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'previous month' button.", + "type": "text" + } +} diff --git a/packages/flutter/lib/src/material/i18n/material_pt.arb b/packages/flutter/lib/src/material/i18n/material_pt.arb new file mode 100644 index 00000000000..a346b4b14d3 --- /dev/null +++ b/packages/flutter/lib/src/material/i18n/material_pt.arb @@ -0,0 +1,27 @@ +{ + "openAppDrawerTooltip": "Abrir menu de navegação", + "@openAppDrawerTooltip": { + "description": "The tooltip for the leading AppBar menu (aka 'hamburger') button", + "type": "text" + }, + "backButtonTooltip": "Costas", + "@backButtonTooltip": { + "description": "The BackButton's tooltip", + "type": "text" + }, + "closeButtonTooltip": "Fechar", + "@closeButtonTooltip": { + "description": "The CloseButton's tooltip", + "type": "text" + }, + "nextMonthTooltip": "Próximo mês", + "@nextMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'next month' button.", + "type": "text" + }, + "previousMonthTooltip": "Mês anterior", + "@previousMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'previous month' button.", + "type": "text" + } +} diff --git a/packages/flutter/lib/src/material/i18n/material_ru.arb b/packages/flutter/lib/src/material/i18n/material_ru.arb new file mode 100644 index 00000000000..c12a616385b --- /dev/null +++ b/packages/flutter/lib/src/material/i18n/material_ru.arb @@ -0,0 +1,27 @@ +{ + "openAppDrawerTooltip": "Открыть меню навигации", + "@openAppDrawerTooltip": { + "description": "The tooltip for the leading AppBar menu (aka 'hamburger') button", + "type": "text" + }, + "backButtonTooltip": "назад", + "@backButtonTooltip": { + "description": "The BackButton's tooltip", + "type": "text" + }, + "closeButtonTooltip": "Закрыть", + "@closeButtonTooltip": { + "description": "The CloseButton's tooltip", + "type": "text" + }, + "nextMonthTooltip": "В следующем месяце", + "@nextMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'next month' button.", + "type": "text" + }, + "previousMonthTooltip": "Предыдущий месяц", + "@previousMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'previous month' button.", + "type": "text" + } +} diff --git a/packages/flutter/lib/src/material/i18n/material_zh.arb b/packages/flutter/lib/src/material/i18n/material_zh.arb new file mode 100644 index 00000000000..c194874f280 --- /dev/null +++ b/packages/flutter/lib/src/material/i18n/material_zh.arb @@ -0,0 +1,27 @@ +{ + "openAppDrawerTooltip": "打开导航菜单", + "@openAppDrawerTooltip": { + "description": "The tooltip for the leading AppBar menu (aka 'hamburger') button", + "type": "text" + }, + "backButtonTooltip": "背部", + "@backButtonTooltip": { + "description": "The BackButton's tooltip", + "type": "text" + }, + "closeButtonTooltip": "关", + "@closeButtonTooltip": { + "description": "The CloseButton's tooltip", + "type": "text" + }, + "nextMonthTooltip": "-下月就29了。", + "@nextMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'next month' button.", + "type": "text" + }, + "previousMonthTooltip": "前一个月", + "@previousMonthTooltip": { + "description": "The tooltip for the MonthPicker's 'previous month' button.", + "type": "text" + } +} diff --git a/packages/flutter/lib/src/material/material_localizations.dart b/packages/flutter/lib/src/material/material_localizations.dart index b13adce5ac5..6289bf4e1f7 100644 --- a/packages/flutter/lib/src/material/material_localizations.dart +++ b/packages/flutter/lib/src/material/material_localizations.dart @@ -2,35 +2,34 @@ // 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'; -/// Interface for localized resource values for the material widgets. +import 'i18n/localizations.dart'; + +/// Defines the localized resource values used by the Material widgts. /// -/// This class provides a default placeholder implementation that returns -/// hard-coded American English values. -class MaterialLocalizations { - /// Create a placeholder object for the localized resources of material widgets - /// which only provides American English strings. - const MaterialLocalizations(); - - /// The locale for which the values of this class's localized resources - /// have been translated. - Locale get locale => const Locale('en', 'US'); - +/// See also: +/// +/// * [DefaultMaterialLocalizations], which implements this interface and +/// and supports a variety of locales. +abstract class MaterialLocalizations { /// The tooltip for the leading [AppBar] menu (aka 'hamburger') button - String get openAppDrawerTooltip => 'Open navigation menu'; + String get openAppDrawerTooltip; /// The [BackButton]'s tooltip. - String get backButtonTooltip => 'Back'; + String get backButtonTooltip; /// The [CloseButton]'s tooltip. - String get closeButtonTooltip => 'Close'; + String get closeButtonTooltip; /// The tooltip for the [MonthPicker]'s "next month" button. - String get nextMonthTooltip => 'Next month'; + String get nextMonthTooltip; /// The tooltip for the [MonthPicker]'s "previous month" button. - String get previousMonthTooltip => 'Previous month'; + String get previousMonthTooltip; /// The `MaterialLocalizations` from the closest [Localizations] instance /// that encloses the given context. @@ -48,3 +47,46 @@ class MaterialLocalizations { return Localizations.of(context, MaterialLocalizations); } } + +/// Localized strings for the material widgets. +class DefaultMaterialLocalizations implements MaterialLocalizations { + /// Construct an object that defines the material widgets' localized strings + /// for the given `locale`. + DefaultMaterialLocalizations(this.locale) { + assert(locale != null); + _nameToValue = localizations[locale.toString()] + ?? localizations[locale.languageCode] + ?? localizations['en'] + ?? {}; + } + + Map _nameToValue; + + /// The locale for which the values of this class's localized resources + /// have been translated. + final Locale locale; + + @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"]; + + /// 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 load(Locale locale) { + return new SynchronousFuture(new DefaultMaterialLocalizations(locale)); + } +} diff --git a/packages/flutter/lib/src/widgets/localizations.dart b/packages/flutter/lib/src/widgets/localizations.dart index 961fbc60819..5ed7d603f23 100644 --- a/packages/flutter/lib/src/widgets/localizations.dart +++ b/packages/flutter/lib/src/widgets/localizations.dart @@ -16,10 +16,17 @@ import 'framework.dart'; // class Intl { static String message(String s, { String name, String locale }) => ''; } // Future initializeMessages(String locale) => null; +// Used by loadAll() to record LocalizationsDelegate.load() futures we're +// waiting for. +class _Pending { + _Pending(this.delegate, this.futureValue); + final LocalizationsDelegate delegate; + final Future futureValue; +} + // A utility function used by Localizations to generate one future // that completes when all of the LocalizationsDelegate.load() futures -// complete. The returned map is indexed by the type of each input -// future's value. +// complete. The returned map is indexed by each delegate's type. // // The input future values must have distinct types. // @@ -31,38 +38,41 @@ import 'framework.dart'; // This is more complicated than just applying Future.wait to input // because some of the input.values may be SynchronousFutures. We don't want // to Future.wait for the synchronous futures. -Future> _loadAll(Iterable> inputValues) { +Future> _loadAll(Locale locale, Iterable> delegates) { final Map output = {}; - List> outputFutures; + List<_Pending> pendingList; - for (Future inputValue in inputValues) { + for (LocalizationsDelegate delegate in delegates) { + final Future inputValue = delegate.load(locale); dynamic completedValue; final Future futureValue = inputValue.then((dynamic value) { return completedValue = value; }); if (completedValue != null) { // inputValue was a SynchronousFuture - final Type type = completedValue.runtimeType; + final Type type = delegate.type; assert(!output.containsKey(type)); output[type] = completedValue; } else { - outputFutures ??= >[]; - outputFutures.add(futureValue); + pendingList ??= <_Pending>[]; + pendingList.add(new _Pending(delegate, futureValue)); } } - // All of the input.values were synchronous futures, we're done. - if (outputFutures == null) + // All of the delegate.load() values were synchronous futures, we're done. + if (pendingList == null) return new SynchronousFuture>(output); - // Some of input.values were asynchronous futures. Wait for them. - return Future.wait(outputFutures).then>((List values) { - for (dynamic value in values) { - final Type type = value.runtimeType; - assert(!output.containsKey(type)); - output[type] = value; - } - return output; - }); + // Some of delegate.load() values were asynchronous futures. Wait for them. + return Future.wait(pendingList.map((_Pending p) => p.futureValue)) + .then>((List values) { + assert(values.length == pendingList.length); + for (int i = 0; i < values.length; i += 1) { + final Type type = pendingList[i].delegate.type; + assert(!output.containsKey(type)); + output[type] = values[i]; + } + return output; + }); } /// A factory for a set of localized resources of type `T`, to be loaded by a @@ -92,8 +102,10 @@ abstract class LocalizationsDelegate { /// after [load] has completed. bool shouldReload(covariant LocalizationsDelegate old); + Type get type => T; + @override - String toString() => '$runtimeType'; + String toString() => '$runtimeType[$type]'; } /// Interface for localized resource values for the lowest levels of the Flutter @@ -346,12 +358,8 @@ class _LocalizationsState extends State { return; } - final Iterable> allResources = delegates.map((LocalizationsDelegate delegate) { - return delegate.load(locale); - }); - Map typeToResources; - final Future> typeToResourcesFuture = _loadAll(allResources) + final Future> typeToResourcesFuture = _loadAll(locale, delegates) .then((Map value) { return typeToResources = value; }); @@ -383,7 +391,6 @@ class _LocalizationsState extends State { T resourcesFor(Type type) { assert(type != null); final T resources = _typeToResources[type]; - assert(resources.runtimeType == type); return resources; } diff --git a/packages/flutter/test/material/localizations_test.dart b/packages/flutter/test/material/localizations_test.dart new file mode 100644 index 00000000000..d6ed0d877d2 --- /dev/null +++ b/packages/flutter/test/material/localizations_test.dart @@ -0,0 +1,52 @@ +// 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_test/flutter_test.dart'; + +Widget buildFrame({ + Locale locale, + WidgetBuilder buildContent, +}) { + return new MaterialApp( + color: const Color(0xFFFFFFFF), + locale: locale, + onGenerateRoute: (RouteSettings settings) { + return new MaterialPageRoute( + builder: (BuildContext context) { + return buildContent(context); + } + ); + }, + ); +} + +void main() { + final Key textKey = new UniqueKey(); + + testWidgets('sanity check', (WidgetTester tester) async { + await tester.pumpWidget( + buildFrame( + buildContent: (BuildContext context) { + return new Text( + MaterialLocalizations.of(context).backButtonTooltip, + key: textKey, + ); + } + ) + ); + + expect(tester.widget(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(find.byKey(textKey)).data, 'Espalda'); + + // Unrecognized locale falls back to 'en' + await tester.binding.setLocale('foo', 'bar'); + await tester.pump(); + expect(tester.widget(find.byKey(textKey)).data, 'Back'); + }); +}