Unverified Commit 4451ffca authored by Per Classon's avatar Per Classon Committed by GitHub

Add option for deferred loading to gen_l10n (#53824)

parent 0d459f23
...@@ -81,6 +81,24 @@ void main(List<String> arguments) { ...@@ -81,6 +81,24 @@ void main(List<String> arguments) {
'Alternatively, see the `header` option to pass in a string ' 'Alternatively, see the `header` option to pass in a string '
'for a simpler header.' 'for a simpler header.'
); );
parser.addFlag(
'use-deferred-loading',
defaultsTo: false,
help: 'Whether to generate the Dart localization file with locales imported'
' as deferred, allowing for lazy loading of each locale in Flutter web.\n'
'\n'
'This can reduce a web app’s initial startup time by decreasing the '
'size of the JavaScript bundle. When this flag is set to true, the '
'messages for a particular locale are only downloaded and loaded by the '
'Flutter app as they are needed. For projects with a lot of different '
'locales and many localization strings, it can be an performance '
'improvement to have deferred loading. For projects with a small number '
'of locales, the difference is negligible, and might slow down the start '
'up compared to bundling the localizations with the rest of the '
'application.\n\n'
'Note that this flag does not affect other platforms such as mobile or '
'desktop.',
);
final argslib.ArgResults results = parser.parse(arguments); final argslib.ArgResults results = parser.parse(arguments);
if (results['help'] == true) { if (results['help'] == true) {
...@@ -98,6 +116,7 @@ void main(List<String> arguments) { ...@@ -98,6 +116,7 @@ void main(List<String> arguments) {
final String preferredSupportedLocaleString = results['preferred-supported-locales'] as String; final String preferredSupportedLocaleString = results['preferred-supported-locales'] as String;
final String headerString = results['header'] as String; final String headerString = results['header'] as String;
final String headerFile = results['header-file'] as String; final String headerFile = results['header-file'] as String;
final bool useDeferredLoading = results['use-deferred-loading'] as bool;
const local.LocalFileSystem fs = local.LocalFileSystem(); const local.LocalFileSystem fs = local.LocalFileSystem();
final LocalizationsGenerator localizationsGenerator = LocalizationsGenerator(fs); final LocalizationsGenerator localizationsGenerator = LocalizationsGenerator(fs);
...@@ -112,6 +131,7 @@ void main(List<String> arguments) { ...@@ -112,6 +131,7 @@ void main(List<String> arguments) {
preferredSupportedLocaleString: preferredSupportedLocaleString, preferredSupportedLocaleString: preferredSupportedLocaleString,
headerString: headerString, headerString: headerString,
headerFile: headerFile, headerFile: headerFile,
useDeferredLoading: useDeferredLoading,
) )
..loadResources() ..loadResources()
..writeOutputFile() ..writeOutputFile()
......
...@@ -206,7 +206,10 @@ String generateBaseClassMethod(Message message) { ...@@ -206,7 +206,10 @@ String generateBaseClassMethod(Message message) {
.replaceAll('@(name)', message.resourceId); .replaceAll('@(name)', message.resourceId);
} }
String _generateLookupByAllCodes(AppResourceBundleCollection allBundles, String className) { String _generateLookupByAllCodes(
AppResourceBundleCollection allBundles,
String Function(LocaleInfo) generateSwitchClauseTemplate,
) {
final Iterable<LocaleInfo> localesWithAllCodes = allBundles.locales.where((LocaleInfo locale) { final Iterable<LocaleInfo> localesWithAllCodes = allBundles.locales.where((LocaleInfo locale) {
return locale.scriptCode != null && locale.countryCode != null; return locale.scriptCode != null && locale.countryCode != null;
}); });
...@@ -216,9 +219,8 @@ String _generateLookupByAllCodes(AppResourceBundleCollection allBundles, String ...@@ -216,9 +219,8 @@ String _generateLookupByAllCodes(AppResourceBundleCollection allBundles, String
} }
final Iterable<String> switchClauses = localesWithAllCodes.map<String>((LocaleInfo locale) { final Iterable<String> switchClauses = localesWithAllCodes.map<String>((LocaleInfo locale) {
return switchClauseTemplate return generateSwitchClauseTemplate(locale)
.replaceAll('@(case)', locale.toString()) .replaceAll('@(case)', locale.toString());
.replaceAll('@(class)', '$className${locale.camelCase()}');
}); });
return allCodesLookupTemplate.replaceAll( return allCodesLookupTemplate.replaceAll(
...@@ -227,7 +229,10 @@ String _generateLookupByAllCodes(AppResourceBundleCollection allBundles, String ...@@ -227,7 +229,10 @@ String _generateLookupByAllCodes(AppResourceBundleCollection allBundles, String
); );
} }
String _generateLookupByScriptCode(AppResourceBundleCollection allBundles, String className) { String _generateLookupByScriptCode(
AppResourceBundleCollection allBundles,
String Function(LocaleInfo) generateSwitchClauseTemplate,
) {
final Iterable<String> switchClauses = allBundles.languages.map((String language) { final Iterable<String> switchClauses = allBundles.languages.map((String language) {
final Iterable<LocaleInfo> locales = allBundles.localesForLanguage(language); final Iterable<LocaleInfo> locales = allBundles.localesForLanguage(language);
final Iterable<LocaleInfo> localesWithScriptCodes = locales.where((LocaleInfo locale) { final Iterable<LocaleInfo> localesWithScriptCodes = locales.where((LocaleInfo locale) {
...@@ -240,11 +245,9 @@ String _generateLookupByScriptCode(AppResourceBundleCollection allBundles, Strin ...@@ -240,11 +245,9 @@ String _generateLookupByScriptCode(AppResourceBundleCollection allBundles, Strin
return nestedSwitchTemplate return nestedSwitchTemplate
.replaceAll('@(languageCode)', language) .replaceAll('@(languageCode)', language)
.replaceAll('@(code)', 'scriptCode') .replaceAll('@(code)', 'scriptCode')
.replaceAll('@(class)', '$className${LocaleInfo.fromString(language).camelCase()}')
.replaceAll('@(switchClauses)', localesWithScriptCodes.map((LocaleInfo locale) { .replaceAll('@(switchClauses)', localesWithScriptCodes.map((LocaleInfo locale) {
return switchClauseTemplate return generateSwitchClauseTemplate(locale)
.replaceAll('@(case)', locale.scriptCode) .replaceAll('@(case)', locale.scriptCode);
.replaceAll('@(class)', '$className${locale.camelCase()}');
}).join('\n ')); }).join('\n '));
}).where((String switchClause) => switchClause != null); }).where((String switchClause) => switchClause != null);
...@@ -258,7 +261,10 @@ String _generateLookupByScriptCode(AppResourceBundleCollection allBundles, Strin ...@@ -258,7 +261,10 @@ String _generateLookupByScriptCode(AppResourceBundleCollection allBundles, Strin
); );
} }
String _generateLookupByCountryCode(AppResourceBundleCollection allBundles, String className) { String _generateLookupByCountryCode(
AppResourceBundleCollection allBundles,
String Function(LocaleInfo) generateSwitchClauseTemplate,
) {
final Iterable<String> switchClauses = allBundles.languages.map((String language) { final Iterable<String> switchClauses = allBundles.languages.map((String language) {
final Iterable<LocaleInfo> locales = allBundles.localesForLanguage(language); final Iterable<LocaleInfo> locales = allBundles.localesForLanguage(language);
final Iterable<LocaleInfo> localesWithCountryCodes = locales.where((LocaleInfo locale) { final Iterable<LocaleInfo> localesWithCountryCodes = locales.where((LocaleInfo locale) {
...@@ -271,11 +277,9 @@ String _generateLookupByCountryCode(AppResourceBundleCollection allBundles, Stri ...@@ -271,11 +277,9 @@ String _generateLookupByCountryCode(AppResourceBundleCollection allBundles, Stri
return nestedSwitchTemplate return nestedSwitchTemplate
.replaceAll('@(languageCode)', language) .replaceAll('@(languageCode)', language)
.replaceAll('@(code)', 'countryCode') .replaceAll('@(code)', 'countryCode')
.replaceAll('@(class)', '$className${LocaleInfo.fromString(language).camelCase()}')
.replaceAll('@(switchClauses)', localesWithCountryCodes.map((LocaleInfo locale) { .replaceAll('@(switchClauses)', localesWithCountryCodes.map((LocaleInfo locale) {
return switchClauseTemplate return generateSwitchClauseTemplate(locale)
.replaceAll('@(case)', locale.countryCode) .replaceAll('@(case)', locale.countryCode);
.replaceAll('@(class)', '$className${locale.camelCase()}');
}).join('\n ')); }).join('\n '));
}).where((String switchClause) => switchClause != null); }).where((String switchClause) => switchClause != null);
...@@ -288,7 +292,10 @@ String _generateLookupByCountryCode(AppResourceBundleCollection allBundles, Stri ...@@ -288,7 +292,10 @@ String _generateLookupByCountryCode(AppResourceBundleCollection allBundles, Stri
.replaceAll('@(switchClauses)', switchClauses.join('\n ')); .replaceAll('@(switchClauses)', switchClauses.join('\n '));
} }
String _generateLookupByLanguageCode(AppResourceBundleCollection allBundles, String className) { String _generateLookupByLanguageCode(
AppResourceBundleCollection allBundles,
String Function(LocaleInfo) generateSwitchClauseTemplate,
) {
final Iterable<String> switchClauses = allBundles.languages.map((String language) { final Iterable<String> switchClauses = allBundles.languages.map((String language) {
final Iterable<LocaleInfo> locales = allBundles.localesForLanguage(language); final Iterable<LocaleInfo> locales = allBundles.localesForLanguage(language);
final Iterable<LocaleInfo> localesWithLanguageCode = locales.where((LocaleInfo locale) { final Iterable<LocaleInfo> localesWithLanguageCode = locales.where((LocaleInfo locale) {
...@@ -299,9 +306,8 @@ String _generateLookupByLanguageCode(AppResourceBundleCollection allBundles, Str ...@@ -299,9 +306,8 @@ String _generateLookupByLanguageCode(AppResourceBundleCollection allBundles, Str
return null; return null;
return localesWithLanguageCode.map((LocaleInfo locale) { return localesWithLanguageCode.map((LocaleInfo locale) {
return switchClauseTemplate return generateSwitchClauseTemplate(locale)
.replaceAll('@(case)', locale.languageCode) .replaceAll('@(case)', locale.languageCode);
.replaceAll('@(class)', '$className${locale.camelCase()}');
}).join('\n '); }).join('\n ');
}).where((String switchClause) => switchClause != null); }).where((String switchClause) => switchClause != null);
...@@ -314,12 +320,67 @@ String _generateLookupByLanguageCode(AppResourceBundleCollection allBundles, Str ...@@ -314,12 +320,67 @@ String _generateLookupByLanguageCode(AppResourceBundleCollection allBundles, Str
.replaceAll('@(switchClauses)', switchClauses.join('\n ')); .replaceAll('@(switchClauses)', switchClauses.join('\n '));
} }
String _generateLookupBody(AppResourceBundleCollection allBundles, String className) { String _generateLookupBody(
AppResourceBundleCollection allBundles,
String className,
bool useDeferredLoading,
String fileName,
) {
final String Function(LocaleInfo) generateSwitchClauseTemplate = (LocaleInfo locale) {
return (useDeferredLoading ?
switchClauseDeferredLoadingTemplate : switchClauseTemplate)
.replaceAll('@(localeClass)', '$className${locale.camelCase()}')
.replaceAll('@(appClass)', className)
.replaceAll('@(library)', '${fileName}_${locale.languageCode}');
};
return lookupBodyTemplate return lookupBodyTemplate
.replaceAll('@(lookupAllCodesSpecified)', _generateLookupByAllCodes(allBundles, className)) .replaceAll('@(lookupAllCodesSpecified)', _generateLookupByAllCodes(
.replaceAll('@(lookupScriptCodeSpecified)', _generateLookupByScriptCode(allBundles, className)) allBundles,
.replaceAll('@(lookupCountryCodeSpecified)', _generateLookupByCountryCode(allBundles, className)) generateSwitchClauseTemplate,
.replaceAll('@(lookupLanguageCodeSpecified)', _generateLookupByLanguageCode(allBundles, className)); ))
.replaceAll('@(lookupScriptCodeSpecified)', _generateLookupByScriptCode(
allBundles,
generateSwitchClauseTemplate,
))
.replaceAll('@(lookupCountryCodeSpecified)', _generateLookupByCountryCode(
allBundles,
generateSwitchClauseTemplate,
))
.replaceAll('@(lookupLanguageCodeSpecified)', _generateLookupByLanguageCode(
allBundles,
generateSwitchClauseTemplate,
));
}
String _generateDelegateClass({
AppResourceBundleCollection allBundles,
String className,
Set<String> supportedLanguageCodes,
bool useDeferredLoading,
String fileName,
}) {
final String lookupBody = _generateLookupBody(
allBundles,
className,
useDeferredLoading,
fileName,
);
final String loadBody = (
useDeferredLoading ? loadBodyDeferredLoadingTemplate : loadBodyTemplate
)
.replaceAll('@(class)', className)
.replaceAll('@(lookupName)', '_lookup$className');
final String lookupFunction = (useDeferredLoading ?
lookupFunctionDeferredLoadingTemplate : lookupFunctionTemplate)
.replaceAll('@(class)', className)
.replaceAll('@(lookupName)', '_lookup$className')
.replaceAll('@(lookupBody)', lookupBody);
return delegateClassTemplate
.replaceAll('@(class)', className)
.replaceAll('@(loadBody)', loadBody)
.replaceAll('@(supportedLanguageCodes)', supportedLanguageCodes.join(', '))
.replaceAll('@(lookupFunction)', lookupFunction);
} }
class LocalizationsGenerator { class LocalizationsGenerator {
...@@ -397,6 +458,23 @@ class LocalizationsGenerator { ...@@ -397,6 +458,23 @@ class LocalizationsGenerator {
final Map<LocaleInfo, List<String>> _unimplementedMessages = <LocaleInfo, List<String>>{}; final Map<LocaleInfo, List<String>> _unimplementedMessages = <LocaleInfo, List<String>>{};
/// Whether to generate the Dart localization file with locales imported as
/// deferred, allowing for lazy loading of each locale in Flutter web.
///
/// This can reduce a web app’s initial startup time by decreasing the size of
/// the JavaScript bundle. When [_useDeferredLoading] is set to true, the
/// messages for a particular locale are only downloaded and loaded by the
/// Flutter app as they are needed. For projects with a lot of different
/// locales and many localization strings, it can be an performance
/// improvement to have deferred loading. For projects with a small number of
/// locales, the difference is negligible, and might slow down the start up
/// compared to bundling the localizations with the rest of the application.
///
/// Note that this flag does not affect other platforms such as mobile or
/// desktop.
bool get useDeferredLoading => _useDeferredLoading;
bool _useDeferredLoading;
/// Initializes [l10nDirectory], [templateArbFile], [outputFile] and [className]. /// Initializes [l10nDirectory], [templateArbFile], [outputFile] and [className].
/// ///
/// Throws an [L10nException] when a provided configuration is not allowed /// Throws an [L10nException] when a provided configuration is not allowed
...@@ -412,12 +490,14 @@ class LocalizationsGenerator { ...@@ -412,12 +490,14 @@ class LocalizationsGenerator {
String preferredSupportedLocaleString, String preferredSupportedLocaleString,
String headerString, String headerString,
String headerFile, String headerFile,
bool useDeferredLoading = false,
}) { }) {
setL10nDirectory(l10nDirectoryPath); setL10nDirectory(l10nDirectoryPath);
setTemplateArbFile(templateArbFileName); setTemplateArbFile(templateArbFileName);
setOutputFile(outputFileString); setOutputFile(outputFileString);
setPreferredSupportedLocales(preferredSupportedLocaleString); setPreferredSupportedLocales(preferredSupportedLocaleString);
_setHeader(headerString, headerFile); _setHeader(headerString, headerFile);
_setUseDeferredLoading(useDeferredLoading);
className = classNameString; className = classNameString;
} }
...@@ -550,6 +630,13 @@ class LocalizationsGenerator { ...@@ -550,6 +630,13 @@ class LocalizationsGenerator {
} }
} }
void _setUseDeferredLoading(bool useDeferredLoading) {
if (useDeferredLoading == null) {
throw L10nException('useDeferredLoading argument cannot be null.');
}
_useDeferredLoading = useDeferredLoading;
}
static bool _isValidGetterAndMethodName(String name) { static bool _isValidGetterAndMethodName(String name) {
// Public Dart method name must not start with an underscore // Public Dart method name must not start with an underscore
if (name[0] == '_') if (name[0] == '_')
...@@ -746,13 +833,26 @@ class LocalizationsGenerator { ...@@ -746,13 +833,26 @@ class LocalizationsGenerator {
} }
} }
final Iterable<String> localeImports = supportedLocales final List<String> sortedClassImports = supportedLocales
.where((LocaleInfo locale) => isBaseClassLocale(locale, locale.languageCode)) .where((LocaleInfo locale) => isBaseClassLocale(locale, locale.languageCode))
.map((LocaleInfo locale) { .map((LocaleInfo locale) {
return "import '${fileName}_${locale.toString()}.dart';"; final String library = '${fileName}_${locale.toString()}';
}); if (useDeferredLoading) {
return "import '$library.dart' deferred as $library;";
final String lookupBody = _generateLookupBody(_allBundles, className); } else {
return "import '$library.dart';";
}
})
.toList()
..sort();
final String delegateClass = _generateDelegateClass(
allBundles: _allBundles,
className: className,
supportedLanguageCodes: supportedLanguageCodes,
useDeferredLoading: useDeferredLoading,
fileName: fileName,
);
return fileTemplate return fileTemplate
.replaceAll('@(header)', header) .replaceAll('@(header)', header)
...@@ -761,9 +861,8 @@ class LocalizationsGenerator { ...@@ -761,9 +861,8 @@ class LocalizationsGenerator {
.replaceAll('@(importFile)', '$directory/$outputFileName') .replaceAll('@(importFile)', '$directory/$outputFileName')
.replaceAll('@(supportedLocales)', supportedLocalesCode.join(',\n ')) .replaceAll('@(supportedLocales)', supportedLocalesCode.join(',\n '))
.replaceAll('@(supportedLanguageCodes)', supportedLanguageCodes.join(', ')) .replaceAll('@(supportedLanguageCodes)', supportedLanguageCodes.join(', '))
.replaceAll('@(messageClassImports)', localeImports.join('\n')) .replaceAll('@(messageClassImports)', sortedClassImports.join('\n'))
.replaceAll('@(lookupName)', '_lookup$className') .replaceAll('@(delegateClass)', delegateClass);
.replaceAll('@(lookupBody)', lookupBody);
} }
void writeOutputFile() { void writeOutputFile() {
......
...@@ -6,6 +6,7 @@ const String fileTemplate = ''' ...@@ -6,6 +6,7 @@ const String fileTemplate = '''
@(header) @(header)
import 'dart:async'; import 'dart:async';
// ignore: unused_import
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'; import 'package:flutter_localizations/flutter_localizations.dart';
...@@ -104,26 +105,7 @@ abstract class @(class) { ...@@ -104,26 +105,7 @@ abstract class @(class) {
@(methods)} @(methods)}
class _@(class)Delegate extends LocalizationsDelegate<@(class)> { @(delegateClass)
const _@(class)Delegate();
@override
Future<@(class)> load(Locale locale) {
return SynchronousFuture<@(class)>(@(lookupName)(locale));
}
@override
bool isSupported(Locale locale) => <String>[@(supportedLanguageCodes)].contains(locale.languageCode);
@override
bool shouldReload(_@(class)Delegate old) => false;
}
@(class) @(lookupName)(Locale locale) {
@(lookupBody)
assert(false, '@(class).delegate failed to load unsupported locale "\$locale"');
return null;
}
'''; ''';
const String numberFormatTemplate = ''' const String numberFormatTemplate = '''
...@@ -205,14 +187,67 @@ const String baseClassMethodTemplate = ''' ...@@ -205,14 +187,67 @@ const String baseClassMethodTemplate = '''
String @(name)(@(parameters)); String @(name)(@(parameters));
'''; ''';
// DELEGATE CLASS TEMPLATES
const String delegateClassTemplate = '''
class _@(class)Delegate extends LocalizationsDelegate<@(class)> {
const _@(class)Delegate();
@override
Future<@(class)> load(Locale locale) {
@(loadBody)
}
@override
bool isSupported(Locale locale) => <String>[@(supportedLanguageCodes)].contains(locale.languageCode);
@override
bool shouldReload(_@(class)Delegate old) => false;
}
@(lookupFunction)''';
const String loadBodyTemplate = '''return SynchronousFuture<@(class)>(@(lookupName)(locale));''';
const String loadBodyDeferredLoadingTemplate = '''return @(lookupName)(locale);''';
// DELEGATE LOOKUP TEMPLATES // DELEGATE LOOKUP TEMPLATES
const String lookupFunctionTemplate = '''
@(class) @(lookupName)(Locale locale) {
@(lookupBody)
assert(false, '@(class).delegate failed to load unsupported locale "\$locale"');
return null;
}''';
const String lookupFunctionDeferredLoadingTemplate = '''
/// Lazy load the library for web, on other platforms we return the
/// localizations synchronously.
Future<@(class)> _loadLibraryForWeb(
Future<dynamic> Function() loadLibrary,
@(class) Function() localizationClosure,
) {
if (kIsWeb) {
return loadLibrary().then((dynamic _) => localizationClosure());
} else {
return SynchronousFuture<@(class)>(localizationClosure());
}
}
Future<@(class)> @(lookupName)(Locale locale) {
@(lookupBody)
assert(false, '@(class).delegate failed to load unsupported locale "\$locale"');
return null;
}''';
const String lookupBodyTemplate = '''@(lookupAllCodesSpecified) const String lookupBodyTemplate = '''@(lookupAllCodesSpecified)
@(lookupScriptCodeSpecified) @(lookupScriptCodeSpecified)
@(lookupCountryCodeSpecified) @(lookupCountryCodeSpecified)
@(lookupLanguageCodeSpecified)'''; @(lookupLanguageCodeSpecified)''';
const String switchClauseTemplate = '''case '@(case)': return @(class)();'''; const String switchClauseTemplate = '''case '@(case)': return @(localeClass)();''';
const String switchClauseDeferredLoadingTemplate = '''case '@(case)': return _loadLibraryForWeb(@(library).loadLibrary, () => @(library).@(localeClass)());''';
const String nestedSwitchTemplate = '''case '@(languageCode)': { const String nestedSwitchTemplate = '''case '@(languageCode)': {
switch (locale.@(code)) { switch (locale.@(code)) {
......
...@@ -380,6 +380,28 @@ void main() { ...@@ -380,6 +380,28 @@ void main() {
fail('Setting headerFile that does not exist should fail'); fail('Setting headerFile that does not exist should fail');
}); });
test('setting useDefferedLoading to null should fail', () {
_standardFlutterDirectoryL10nSetup(fs);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
headerString: '/// Sample header',
useDeferredLoading: null,
);
} on L10nException catch (e) {
expect(e.message, contains('useDeferredLoading argument cannot be null.'));
return;
}
fail('Setting useDefferedLoading to null should fail');
});
group('loadResources', () { group('loadResources', () {
test('correctly initializes supportedLocales and supportedLanguageCodes properties', () { test('correctly initializes supportedLocales and supportedLanguageCodes properties', () {
_standardFlutterDirectoryL10nSetup(fs); _standardFlutterDirectoryL10nSetup(fs);
...@@ -792,6 +814,67 @@ void main() { ...@@ -792,6 +814,67 @@ void main() {
expect(englishLocalizationsFile, contains('class AppLocalizationsEn extends AppLocalizations')); expect(englishLocalizationsFile, contains('class AppLocalizationsEn extends AppLocalizations'));
}); });
test('language imports are sorted when preferredSupportedLocaleString is given', () {
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true)
..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString)
..childFile('app_zh.arb').writeAsStringSync(singleZhMessageArbFileString)
..childFile('app_es.arb').writeAsStringSync(singleEsMessageArbFileString);
const String preferredSupportedLocaleString = '["zh"]';
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
preferredSupportedLocaleString: preferredSupportedLocaleString,
);
generator.loadResources();
generator.writeOutputFile();
} on Exception catch (e) {
fail('Generating output files should not fail: $e');
}
final String localizationsFile = fs.file(
path.join('lib', 'l10n', defaultOutputFileString),
).readAsStringSync();
expect(localizationsFile, contains(
'''
import '${defaultOutputFileString}_en.dart';
import '${defaultOutputFileString}_es.dart';
import '${defaultOutputFileString}_zh.dart';
'''));
});
test('imports are deferred when useDeferredImports are set', () {
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true)
..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
useDeferredLoading: true,
);
generator.loadResources();
generator.writeOutputFile();
} on Exception catch (e) {
fail('Generating output files should not fail: $e');
}
final String localizationsFile = fs.file(
path.join('lib', 'l10n', defaultOutputFileString),
).readAsStringSync();
expect(localizationsFile, contains(
'''
import '${defaultOutputFileString}_en.dart' deferred as ${defaultOutputFileString}_en;
'''));
});
group('DateTime tests', () { group('DateTime tests', () {
test('throws an exception when improperly formatted date is passed in', () { test('throws an exception when improperly formatted date is passed in', () {
const String singleDateMessageArbFileString = ''' const String singleDateMessageArbFileString = '''
......
...@@ -48,7 +48,7 @@ void main() { ...@@ -48,7 +48,7 @@ void main() {
} }
} }
test('generated l10n classes produce expected localized strings', () async { void setUpAndRunGenL10n({List<String> args}) {
// Get the intl packages before running gen_l10n. // Get the intl packages before running gen_l10n.
final String flutterBin = globals.platform.isWindows ? 'flutter.bat' : 'flutter'; final String flutterBin = globals.platform.isWindows ? 'flutter.bat' : 'flutter';
final String flutterPath = globals.fs.path.join(getFlutterRoot(), 'bin', flutterBin); final String flutterPath = globals.fs.path.join(getFlutterRoot(), 'bin', flutterBin);
...@@ -58,8 +58,10 @@ void main() { ...@@ -58,8 +58,10 @@ void main() {
final String genL10nPath = globals.fs.path.join(getFlutterRoot(), 'dev', 'tools', 'localization', 'bin', 'gen_l10n.dart'); final String genL10nPath = globals.fs.path.join(getFlutterRoot(), 'dev', 'tools', 'localization', 'bin', 'gen_l10n.dart');
final String dartBin = globals.platform.isWindows ? 'dart.exe' : 'dart'; final String dartBin = globals.platform.isWindows ? 'dart.exe' : 'dart';
final String dartPath = globals.fs.path.join(getFlutterRoot(), 'bin', 'cache', 'dart-sdk', 'bin', dartBin); final String dartPath = globals.fs.path.join(getFlutterRoot(), 'bin', 'cache', 'dart-sdk', 'bin', dartBin);
runCommand(<String>[dartPath, genL10nPath]); runCommand(<String>[dartPath, genL10nPath, args?.join(' ')]);
}
Future<StringBuffer> runApp() async {
// Run the app defined in GenL10nProject.main and wait for it to // Run the app defined in GenL10nProject.main and wait for it to
// send '#l10n END' to its stdout. // send '#l10n END' to its stdout.
final Completer<void> l10nEnd = Completer<void>(); final Completer<void> l10nEnd = Completer<void>();
...@@ -75,6 +77,10 @@ void main() { ...@@ -75,6 +77,10 @@ void main() {
await _flutter.run(); await _flutter.run();
await l10nEnd.future; await l10nEnd.future;
await subscription.cancel(); await subscription.cancel();
return stdout;
}
void expectOutput(StringBuffer stdout) {
expect(stdout.toString(), expect(stdout.toString(),
'#l10n 0 (--- supportedLocales tests ---)\n' '#l10n 0 (--- supportedLocales tests ---)\n'
'#l10n 1 (supportedLocales[0]: languageCode: en, countryCode: null, scriptCode: null)\n' '#l10n 1 (supportedLocales[0]: languageCode: en, countryCode: null, scriptCode: null)\n'
...@@ -133,5 +139,17 @@ void main() { ...@@ -133,5 +139,17 @@ void main() {
'#l10n 54 (Flutter is "amazing", times 2!)\n' '#l10n 54 (Flutter is "amazing", times 2!)\n'
'#l10n END\n' '#l10n END\n'
); );
}
test('generated l10n classes produce expected localized strings', () async {
setUpAndRunGenL10n();
final StringBuffer stdout = await runApp();
expectOutput(stdout);
});
test('generated l10n classes produce expected localized strings with deferred loading', () async {
setUpAndRunGenL10n(args: <String>['--use-deferred-loading']);
final StringBuffer stdout = await runApp();
expectOutput(stdout);
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment