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) {
'Alternatively, see the `header` option to pass in a string '
'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);
if (results['help'] == true) {
......@@ -98,6 +116,7 @@ void main(List<String> arguments) {
final String preferredSupportedLocaleString = results['preferred-supported-locales'] as String;
final String headerString = results['header'] 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();
final LocalizationsGenerator localizationsGenerator = LocalizationsGenerator(fs);
......@@ -112,6 +131,7 @@ void main(List<String> arguments) {
preferredSupportedLocaleString: preferredSupportedLocaleString,
headerString: headerString,
headerFile: headerFile,
useDeferredLoading: useDeferredLoading,
)
..loadResources()
..writeOutputFile()
......
This diff is collapsed.
......@@ -6,6 +6,7 @@ const String fileTemplate = '''
@(header)
import 'dart:async';
// ignore: unused_import
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
......@@ -104,26 +105,7 @@ abstract class @(class) {
@(methods)}
class _@(class)Delegate extends LocalizationsDelegate<@(class)> {
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;
}
@(delegateClass)
''';
const String numberFormatTemplate = '''
......@@ -205,14 +187,67 @@ const String baseClassMethodTemplate = '''
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
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)
@(lookupScriptCodeSpecified)
@(lookupCountryCodeSpecified)
@(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)': {
switch (locale.@(code)) {
......
......@@ -380,6 +380,28 @@ void main() {
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', () {
test('correctly initializes supportedLocales and supportedLanguageCodes properties', () {
_standardFlutterDirectoryL10nSetup(fs);
......@@ -792,6 +814,67 @@ void main() {
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', () {
test('throws an exception when improperly formatted date is passed in', () {
const String singleDateMessageArbFileString = '''
......
......@@ -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.
final String flutterBin = globals.platform.isWindows ? 'flutter.bat' : 'flutter';
final String flutterPath = globals.fs.path.join(getFlutterRoot(), 'bin', flutterBin);
......@@ -58,8 +58,10 @@ void main() {
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 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
// send '#l10n END' to its stdout.
final Completer<void> l10nEnd = Completer<void>();
......@@ -75,6 +77,10 @@ void main() {
await _flutter.run();
await l10nEnd.future;
await subscription.cancel();
return stdout;
}
void expectOutput(StringBuffer stdout) {
expect(stdout.toString(),
'#l10n 0 (--- supportedLocales tests ---)\n'
'#l10n 1 (supportedLocales[0]: languageCode: en, countryCode: null, scriptCode: null)\n'
......@@ -133,5 +139,17 @@ void main() {
'#l10n 54 (Flutter is "amazing", times 2!)\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