Unverified Commit e8436970 authored by TabooSun's avatar TabooSun Committed by GitHub

Gen l10n add named argument option (#138663)

Add an option to use named argument for generated method.

Fix #116308
parent 0d6927cb
......@@ -118,4 +118,5 @@ Kim Jiun <kkimj@hanyang.ac.kr>
LinXunFeng <linxunfeng@yeah.net>
Sabin Neupane <sabin.neupane26@gmail.com>
Mahdi Bagheri <1839491@gmail.com>
Mok Kah Wai <taboosun1996@gmail.com>
Lucas Saudon <lsaudon@gmail.com>
......@@ -207,6 +207,10 @@ class GenerateLocalizationsCommand extends FlutterCommand {
'and "}" is treated as a string if it does not close any previous "{" '
'that is treated as a special character.',
);
argParser.addFlag(
'use-named-parameters',
help: 'Whether or not to use named parameters for the generated localization methods.',
);
}
final FileSystem _fileSystem;
......
......@@ -73,6 +73,7 @@ Future<LocalizationsGenerator> generateLocalizations({
logger: logger,
suppressWarnings: options.suppressWarnings,
useRelaxedSyntax: options.relaxSyntax,
useNamedParameters: options.useNamedParameters,
)
..loadResources()
..writeOutputFiles(isFromYaml: true, useCRLF: useCRLF);
......@@ -122,9 +123,9 @@ String _syntheticL10nPackagePath(FileSystem fileSystem) => fileSystem.path.join(
// For example, if placeholders are used for plurals and no type was specified, then the type will
// automatically set to 'num'. Similarly, if such placeholders are used for selects, then the type
// will be set to 'String'. For such placeholders that are used for both, we should throw an error.
List<String> generateMethodParameters(Message message) {
List<String> generateMethodParameters(Message message, bool useNamedParameters) {
return message.placeholders.values.map((Placeholder placeholder) {
return '${placeholder.type} ${placeholder.name}';
return '${useNamedParameters ? 'required ' : ''}${placeholder.type} ${placeholder.name}';
}).toList();
}
......@@ -231,7 +232,7 @@ Map<String, String> pluralCases = <String, String>{
'other': 'other',
};
String generateBaseClassMethod(Message message, LocaleInfo? templateArbLocale) {
String generateBaseClassMethod(Message message, LocaleInfo? templateArbLocale, bool useNamedParameters) {
final String comment = message
.description
?.split('\n')
......@@ -242,11 +243,11 @@ String generateBaseClassMethod(Message message, LocaleInfo? templateArbLocale) {
/// **'${generateString(message.value)}'**''';
if (message.placeholders.isNotEmpty) {
return baseClassMethodTemplate
return (useNamedParameters ? baseClassMethodWithNamedParameterTemplate : baseClassMethodTemplate)
.replaceAll('@(comment)', comment)
.replaceAll('@(templateLocaleTranslationComment)', templateLocaleTranslationComment)
.replaceAll('@(name)', message.resourceId)
.replaceAll('@(parameters)', generateMethodParameters(message).join(', '));
.replaceAll('@(parameters)', generateMethodParameters(message, useNamedParameters).join(', '));
}
return baseClassGetterTemplate
.replaceAll('@(comment)', comment)
......@@ -492,6 +493,7 @@ class LocalizationsGenerator {
required Logger logger,
bool suppressWarnings = false,
bool useRelaxedSyntax = false,
bool useNamedParameters = false,
}) {
final Directory? projectDirectory = projectDirFromPath(fileSystem, projectPathString);
final Directory inputDirectory = inputDirectoryFromPath(fileSystem, inputPathString, projectDirectory);
......@@ -516,6 +518,7 @@ class LocalizationsGenerator {
logger: logger,
suppressWarnings: suppressWarnings,
useRelaxedSyntax: useRelaxedSyntax,
useNamedParameters: useNamedParameters,
);
}
......@@ -541,6 +544,7 @@ class LocalizationsGenerator {
this.useEscaping = false,
this.suppressWarnings = false,
this.useRelaxedSyntax = false,
this.useNamedParameters = false,
});
final FileSystem _fs;
......@@ -685,6 +689,14 @@ class LocalizationsGenerator {
/// Whether or not to suppress warnings or not.
final bool suppressWarnings;
/// Whether to generate the Dart localization methods with named parameters.
///
/// If this sets to true, the generated Dart localization methods will be:
/// ```
/// String helloWorld({required String name});
/// ```
final bool useNamedParameters;
static bool _isNotReadable(FileStat fileStat) {
final String rawStatString = fileStat.modeString();
// Removes potential prepended permission bits, such as '(suid)' and '(guid)'.
......@@ -1124,7 +1136,7 @@ class LocalizationsGenerator {
return fileTemplate
.replaceAll('@(header)', header.isEmpty ? '' : '$header\n')
.replaceAll('@(class)', className)
.replaceAll('@(methods)', _allMessages.map((Message message) => generateBaseClassMethod(message, _templateArbLocale)).join('\n'))
.replaceAll('@(methods)', _allMessages.map((Message message) => generateBaseClassMethod(message, _templateArbLocale, useNamedParameters)).join('\n'))
.replaceAll('@(importFile)', '$directory/$outputFileName')
.replaceAll('@(supportedLocales)', supportedLocalesCode.join(',\n '))
.replaceAll('@(supportedLanguageCodes)', supportedLanguageCodes.join(', '))
......@@ -1306,9 +1318,9 @@ The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", "
}
final String messageString = generateVariables(node, isRoot: true);
final String tempVarLines = tempVariables.isEmpty ? '' : '${tempVariables.join('\n')}\n';
return methodTemplate
return (useNamedParameters ? methodWithNamedParameterTemplate : methodTemplate)
.replaceAll('@(name)', message.resourceId)
.replaceAll('@(parameters)', generateMethodParameters(message).join(', '))
.replaceAll('@(parameters)', generateMethodParameters(message, useNamedParameters).join(', '))
.replaceAll('@(dateFormatting)', generateDateFormattingLogic(message))
.replaceAll('@(numberFormatting)', generateNumberFormattingLogic(message))
.replaceAll('@(tempVars)', tempVarLines)
......
......@@ -142,6 +142,14 @@ const String methodTemplate = '''
@(tempVars) return @(message);
}''';
const String methodWithNamedParameterTemplate = '''
@override
String @(name)({@(parameters)}) {
@(dateFormatting)
@(numberFormatting)
@(tempVars) return @(message);
}''';
const String pluralVariableTemplate = '''
String @(varName) = intl.Intl.pluralLogic(
@(count),
......@@ -195,6 +203,13 @@ const String baseClassMethodTemplate = '''
String @(name)(@(parameters));
''';
const String baseClassMethodWithNamedParameterTemplate = '''
@(comment)
///
@(templateLocaleTranslationComment)
String @(name)({@(parameters)});
''';
// DELEGATE CLASS TEMPLATES
const String delegateClassTemplate = '''
......
......@@ -355,6 +355,7 @@ class LocalizationOptions {
bool? useEscaping,
bool? suppressWarnings,
bool? relaxSyntax,
bool? useNamedParameters,
}) : templateArbFile = templateArbFile ?? 'app_en.arb',
outputLocalizationFile = outputLocalizationFile ?? 'app_localizations.dart',
outputClass = outputClass ?? 'AppLocalizations',
......@@ -365,7 +366,8 @@ class LocalizationOptions {
format = format ?? false,
useEscaping = useEscaping ?? false,
suppressWarnings = suppressWarnings ?? false,
relaxSyntax = relaxSyntax ?? false;
relaxSyntax = relaxSyntax ?? false,
useNamedParameters = useNamedParameters ?? false;
/// The `--arb-dir` argument.
///
......@@ -467,6 +469,14 @@ class LocalizationOptions {
/// This was added in for backward compatibility and is not recommended
/// as it may mask errors.
final bool relaxSyntax;
/// The `use-named-parameters` argument.
///
/// Whether or not to use named parameters for the generated localization
/// methods.
///
/// Defaults to `false`.
final bool useNamedParameters;
}
/// Parse the localizations configuration options from [file].
......@@ -511,6 +521,7 @@ LocalizationOptions parseLocalizationsOptionsFromYAML({
useEscaping: _tryReadBool(yamlNode, 'use-escaping', logger),
suppressWarnings: _tryReadBool(yamlNode, 'suppress-warnings', logger),
relaxSyntax: _tryReadBool(yamlNode, 'relax-syntax', logger),
useNamedParameters: _tryReadBool(yamlNode, 'use-named-parameters', logger),
);
}
......@@ -537,6 +548,7 @@ LocalizationOptions parseLocalizationsOptionsFromCommand({
format: command.boolArg('format'),
useEscaping: command.boolArg('use-escaping'),
suppressWarnings: command.boolArg('suppress-warnings'),
useNamedParameters: command.boolArg('use-named-parameters'),
);
}
......
......@@ -96,6 +96,7 @@ void main() {
bool areResourceAttributeRequired = false,
bool suppressWarnings = false,
bool relaxSyntax = false,
bool useNamedParameters = false,
void Function(Directory)? setup,
}
) {
......@@ -128,6 +129,7 @@ void main() {
areResourceAttributesRequired: areResourceAttributeRequired,
suppressWarnings: suppressWarnings,
useRelaxedSyntax: relaxSyntax,
useNamedParameters: useNamedParameters,
)
..loadResources()
..writeOutputFiles(isFromYaml: isFromYaml);
......@@ -2491,4 +2493,43 @@ NumberFormat.decimalPatternDigits(
setupLocalizations(<String, String>{ 'en': dollarSignWithSelect });
expect(getGeneratedFileContent(locale: 'en'), contains(r'\$nice_bug\nHello Bug! Manifistation #1 $_temp0'));
});
testWithoutContext('can generate method with named parameter', () {
const String arbFile = '''
{
"helloName": "Hello {name}!",
"@helloName": {
"description": "A more personal greeting",
"placeholders": {
"name": {
"type": "String",
"description": "The name of the person to greet"
}
}
},
"helloNameAndAge": "Hello {name}! You are {age} years old.",
"@helloNameAndAge": {
"description": "A more personal greeting",
"placeholders": {
"name": {
"type": "String",
"description": "The name of the person to greet"
},
"age": {
"type": "int",
"description": "The age of the person to greet"
}
}
}
}
''';
setupLocalizations(<String, String>{ 'en': arbFile }, useNamedParameters: true);
final String localizationsFile = getGeneratedFileContent(locale: 'en');
expect(localizationsFile, containsIgnoringWhitespace(r'''
String helloName({required String name}) {
'''));
expect(localizationsFile, containsIgnoringWhitespace(r'''
String helloNameAndAge({required String name, required int age}) {
'''));
});
}
......@@ -11,7 +11,12 @@ import 'test_data/gen_l10n_project.dart';
import 'test_driver.dart';
import 'test_utils.dart';
final GenL10nProject project = GenL10nProject();
final GenL10nProject project = GenL10nProject(
useNamedParameters: false,
);
final GenL10nProject projectWithNamedParameter = GenL10nProject(
useNamedParameters: true,
);
// Verify that the code generated by gen_l10n executes correctly.
// It can fail if gen_l10n produces a lib/l10n/app_localizations.dart that
......@@ -180,4 +185,11 @@ void main() {
final StringBuffer stdout = await runApp();
expectOutput(stdout);
});
testWithoutContext('generated l10n classes produce expected localized strings when named parameter is used', () async {
await projectWithNamedParameter.setUpIn(tempDir);
flutter = FlutterRunTestDriver(tempDir);
final StringBuffer stdout = await runApp();
expectOutput(stdout);
});
}
......@@ -8,6 +8,8 @@ import '../test_utils.dart';
import 'project.dart';
class GenL10nProject extends Project {
GenL10nProject({required this.useNamedParameters});
@override
Future<void> setUpIn(Directory dir, {
bool useDeferredLoading = false,
......@@ -26,10 +28,12 @@ class GenL10nProject extends Project {
writeFile(fileSystem.path.join(dir.path, 'l10n.yaml'), l10nYaml(
useDeferredLoading: useDeferredLoading,
useSyntheticPackage: useSyntheticPackage,
useNamedParameters: useNamedParameters,
));
return super.setUpIn(dir);
}
@override
final String pubspec = '''
name: test_l10n_project
......@@ -44,352 +48,75 @@ dependencies:
intl: any # Pick up the pinned version from flutter_localizations
''';
String? _main;
@override
final String main = r'''
import 'package:flutter/material.dart';
String get main =>
_main ??= (useNamedParameters ? _getMainWithNamedParameters() : _getMain());
import 'l10n/app_localizations.dart';
final bool useNamedParameters;
class LocaleBuilder extends StatelessWidget {
const LocaleBuilder({
Key? key,
this.locale,
this.test,
required this.callback,
}) : super(key: key);
final String appEn = r'''
{
"@@locale": "en",
final Locale? locale;
final String? test;
final void Function (BuildContext context) callback;
"helloWorld": "Hello World",
"@helloWorld": {
"description": "The conventional newborn programmer greeting"
},
@override build(BuildContext context) {
return Localizations.override(
locale: locale,
context: context,
child: ResultBuilder(
test: test,
callback: callback,
),
);
}
}
"helloNewlineWorld": "Hello \n World",
"@helloNewlineWorld": {
"description": "The JSON decoder should convert backslash-n to a newline character in the generated Dart string."
},
class ResultBuilder extends StatelessWidget {
const ResultBuilder({
Key? key,
this.test,
required this.callback,
}) : super(key: key);
"testDollarSign": "Hello $ World",
"@testDollarSign": {
"description": "The generated Dart String should handle the dollar sign correctly."
},
final String? test;
final void Function (BuildContext context) callback;
"hello": "Hello {world}",
"@hello": {
"description": "A message with a single parameter",
"placeholders": {
"world": {}
}
},
@override build(BuildContext context) {
return Builder(
builder: (BuildContext context) {
try {
callback(context);
} on Exception catch (e) {
print('#l10n A(n) $e has occurred trying to generate "$test" results.');
print('#l10n END');
}
return Container();
"greeting": "{hello} {world}",
"@greeting": {
"description": "A message with a two parameters",
"placeholders": {
"hello": {},
"world": {}
}
},
"helloWorldOn": "Hello World on {date}",
"@helloWorldOn": {
"description": "A message with a date parameter",
"placeholders": {
"date": {
"type": "DateTime",
"format": "yMMMMEEEEd"
}
}
},
"helloWorldDuring": "Hello World from {startDate} to {endDate}",
"@helloWorldDuring": {
"description": "A message with two date parameters",
"placeholders": {
"startDate": {
"type": "DateTime",
"format": "y"
},
);
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
final List<String> results = [];
return Row(
children: <Widget>[
LocaleBuilder(
test: 'supportedLocales',
callback: (BuildContext context) {
results.add('--- supportedLocales tests ---');
int n = 0;
for (Locale locale in AppLocalizations.supportedLocales) {
String languageCode = locale.languageCode;
String? countryCode = locale.countryCode;
String? scriptCode = locale.scriptCode;
results.add('supportedLocales[$n]: languageCode: $languageCode, countryCode: $countryCode, scriptCode: $scriptCode');
n += 1;
}
},
),
LocaleBuilder(
locale: Locale('en', 'CA'),
test: 'countryCode - en_CA',
callback: (BuildContext context) {
results.add('--- countryCode (en_CA) tests ---');
results.add(AppLocalizations.of(context)!.helloWorld);
results.add(AppLocalizations.of(context)!.hello("CA fallback World"));
},
),
LocaleBuilder(
locale: Locale('en', 'GB'),
test: 'countryCode - en_GB',
callback: (BuildContext context) {
results.add('--- countryCode (en_GB) tests ---');
results.add(AppLocalizations.of(context)!.helloWorld);
results.add(AppLocalizations.of(context)!.hello("GB fallback World"));
},
),
LocaleBuilder(
locale: Locale('zh'),
test: 'zh',
callback: (BuildContext context) {
results.add('--- zh ---');
results.add(AppLocalizations.of(context)!.helloWorld);
results.add(AppLocalizations.of(context)!.helloWorlds(0));
results.add(AppLocalizations.of(context)!.helloWorlds(1));
results.add(AppLocalizations.of(context)!.helloWorlds(2));
// Should use the fallback language, in this case,
// "Hello 世界" should be displayed.
results.add(AppLocalizations.of(context)!.hello("世界"));
// helloCost is tested in 'zh' because 'es' currency format contains a
// non-breaking space character (U+00A0), which if removed,
// makes it hard to decipher why the test is failing.
results.add(AppLocalizations.of(context)!.helloCost("价钱", 123));
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'),
test: 'zh',
callback: (BuildContext context) {
results.add('--- scriptCode: zh_Hans ---');
results.add(AppLocalizations.of(context)!.helloWorld);
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),
test: 'scriptCode - zh_Hant',
callback: (BuildContext context) {
results.add('--- scriptCode - zh_Hant ---');
results.add(AppLocalizations.of(context)!.helloWorld);
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'zh', countryCode: 'TW', scriptCode: 'Hant'),
test: 'scriptCode - zh_TW_Hant',
callback: (BuildContext context) {
results.add('--- scriptCode - zh_Hant_TW ---');
results.add(AppLocalizations.of(context)!.helloWorld);
},
),
LocaleBuilder(
locale: Locale('en'),
test: 'General formatting',
callback: (BuildContext context) {
results.add('--- General formatting tests ---');
final AppLocalizations localizations = AppLocalizations.of(context)!;
results.addAll(<String>[
'${localizations.helloWorld}',
'${localizations.helloNewlineWorld}',
'${localizations.testDollarSign}',
'${localizations.hello("World")}',
'${localizations.greeting("Hello", "World")}',
'${localizations.helloWorldOn(DateTime(1960))}',
'${localizations.helloOn("world argument", DateTime(1960), DateTime(1960))}',
'${localizations.helloWorldDuring(DateTime(1960), DateTime(2020))}',
'${localizations.helloFor(123)}',
'${localizations.helloCost("price", 123)}',
'${localizations.helloCostWithOptionalParam("price", .5)}',
'${localizations.helloCostWithSpecialCharacter1("price", .5)}',
'${localizations.helloCostWithSpecialCharacter2("price", .5)}',
'${localizations.helloCostWithSpecialCharacter3("price", .5)}',
'${localizations.helloDecimalPattern(1200000)}',
'${localizations.helloPercentPattern(1200000)}',
'${localizations.helloScientificPattern(1200000)}',
'${localizations.helloWorlds(0)}',
'${localizations.helloWorlds(1)}',
'${localizations.helloWorlds(2)}',
'${localizations.helloAdjectiveWorlds(0, "new")}',
'${localizations.helloAdjectiveWorlds(1, "new")}',
'${localizations.helloAdjectiveWorlds(2, "new")}',
'${localizations.helloWorldsOn(0, DateTime(1960))}',
'${localizations.helloWorldsOn(1, DateTime(1960))}',
'${localizations.helloWorldsOn(2, DateTime(1960))}',
'${localizations.helloWorldPopulation(0, 100)}',
'${localizations.helloWorldPopulation(1, 101)}',
'${localizations.helloWorldPopulation(2, 102)}',
'${localizations.helloWorldsInterpolation(123, "Hello", "World")}',
'${localizations.dollarSign}',
'${localizations.dollarSignPlural(1)}',
'${localizations.singleQuote}',
'${localizations.singleQuotePlural(2)}',
'${localizations.doubleQuote}',
'${localizations.doubleQuotePlural(2)}',
"${localizations.vehicleSelect('truck')}",
"${localizations.singleQuoteSelect('sedan')}",
"${localizations.doubleQuoteSelect('cabriolet')}",
"${localizations.pluralInString(1)}",
"${localizations.selectInString('he')}",
"${localizations.selectWithPlaceholder('male', 'ice cream')}",
"${localizations.selectWithPlaceholder('female', 'chocolate')}",
"${localizations.selectInPlural('male', 1)}",
"${localizations.selectInPlural('male', 2)}",
"${localizations.selectInPlural('female', 1)}",
'${localizations.datetime1(DateTime(2023, 6, 26))}',
'${localizations.datetime2(DateTime(2023, 6, 26, 5, 23))}',
]);
},
),
LocaleBuilder(
locale: Locale('es'),
test: '--- es ---',
callback: (BuildContext context) {
results.add('--- es ---');
final AppLocalizations localizations = AppLocalizations.of(context)!;
results.addAll(<String>[
'${localizations.helloWorld}',
'${localizations.helloNewlineWorld}',
'${localizations.testDollarSign}',
'${localizations.hello("Mundo")}',
'${localizations.greeting("Hola", "Mundo")}',
'${localizations.helloWorldOn(DateTime(1960))}',
'${localizations.helloOn("world argument", DateTime(1960), DateTime(1960))}',
'${localizations.helloWorldDuring(DateTime(1960), DateTime(2020))}',
'${localizations.helloFor(123)}',
// helloCost is tested in 'zh' because 'es' currency format contains a
// non-breaking space character (U+00A0), which if removed,
// makes it hard to decipher why the test is failing.
'${localizations.helloWorlds(0)}',
'${localizations.helloWorlds(1)}',
'${localizations.helloWorlds(2)}',
'${localizations.helloAdjectiveWorlds(0, "nuevo")}',
'${localizations.helloAdjectiveWorlds(1, "nuevo")}',
'${localizations.helloAdjectiveWorlds(2, "nuevo")}',
'${localizations.helloWorldsOn(0, DateTime(1960))}',
'${localizations.helloWorldsOn(1, DateTime(1960))}',
'${localizations.helloWorldsOn(2, DateTime(1960))}',
'${localizations.helloWorldPopulation(0, 100)}',
'${localizations.helloWorldPopulation(1, 101)}',
'${localizations.helloWorldPopulation(2, 102)}',
'${localizations.helloWorldsInterpolation(123, "Hola", "Mundo")}',
'${localizations.dollarSign}',
'${localizations.dollarSignPlural(1)}',
'${localizations.singleQuote}',
'${localizations.singleQuotePlural(2)}',
'${localizations.doubleQuote}',
'${localizations.doubleQuotePlural(2)}',
"${localizations.vehicleSelect('truck')}",
"${localizations.singleQuoteSelect('sedan')}",
"${localizations.doubleQuoteSelect('cabriolet')}",
"${localizations.pluralInString(1)}",
"${localizations.selectInString('he')}",
]);
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'es', countryCode: '419'),
test: 'countryCode - es_419',
callback: (BuildContext context) {
results.add('--- es_419 ---');
final AppLocalizations localizations = AppLocalizations.of(context)!;
results.addAll([
'${localizations.helloWorld}',
'${localizations.helloWorlds(0)}',
'${localizations.helloWorlds(1)}',
'${localizations.helloWorlds(2)}',
]);
},
),
LocaleBuilder(
callback: (BuildContext context) {
try {
int n = 0;
for (final String result in results) {
// Newline character replacement is necessary because
// the stream breaks up stdout by new lines.
print('#l10n $n (${result.replaceAll('\n', '_NEWLINE_')})');
n += 1;
}
}
finally {
print('#l10n END');
}
},
),
],
);
}
}
void main() {
runApp(
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Home(),
),
);
}
''';
final String appEn = r'''
{
"@@locale": "en",
"helloWorld": "Hello World",
"@helloWorld": {
"description": "The conventional newborn programmer greeting"
},
"helloNewlineWorld": "Hello \n World",
"@helloNewlineWorld": {
"description": "The JSON decoder should convert backslash-n to a newline character in the generated Dart string."
},
"testDollarSign": "Hello $ World",
"@testDollarSign": {
"description": "The generated Dart String should handle the dollar sign correctly."
},
"hello": "Hello {world}",
"@hello": {
"description": "A message with a single parameter",
"placeholders": {
"world": {}
}
},
"greeting": "{hello} {world}",
"@greeting": {
"description": "A message with a two parameters",
"placeholders": {
"hello": {},
"world": {}
}
},
"helloWorldOn": "Hello World on {date}",
"@helloWorldOn": {
"description": "A message with a date parameter",
"placeholders": {
"date": {
"type": "DateTime",
"format": "yMMMMEEEEd"
}
}
},
"helloWorldDuring": "Hello World from {startDate} to {endDate}",
"@helloWorldDuring": {
"description": "A message with two date parameters",
"placeholders": {
"startDate": {
"type": "DateTime",
"format": "y"
},
"endDate": {
"type": "DateTime",
"format": "y"
}
}
},
"endDate": {
"type": "DateTime",
"format": "y"
}
}
},
"helloOn": "Hello {world} on {date} at {time}",
"@helloOn": {
......@@ -777,18 +504,590 @@ void main() {
}
''';
String l10nYaml({
required bool useDeferredLoading,
required bool useSyntheticPackage,
}) {
String l10nYamlString = '';
String _getMain() => r'''
import 'package:flutter/material.dart';
if (useDeferredLoading) {
l10nYamlString += 'use-deferred-loading: true\n';
}
import 'l10n/app_localizations.dart';
if (!useSyntheticPackage) {
l10nYamlString += 'synthetic-package: false\n';
class LocaleBuilder extends StatelessWidget {
const LocaleBuilder({
Key? key,
this.locale,
this.test,
required this.callback,
}) : super(key: key);
final Locale? locale;
final String? test;
final void Function (BuildContext context) callback;
@override build(BuildContext context) {
return Localizations.override(
locale: locale,
context: context,
child: ResultBuilder(
test: test,
callback: callback,
),
);
}
}
class ResultBuilder extends StatelessWidget {
const ResultBuilder({
Key? key,
this.test,
required this.callback,
}) : super(key: key);
final String? test;
final void Function (BuildContext context) callback;
@override build(BuildContext context) {
return Builder(
builder: (BuildContext context) {
try {
callback(context);
} on Exception catch (e) {
print('#l10n A(n) $e has occurred trying to generate "$test" results.');
print('#l10n END');
}
return Container();
},
);
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
final List<String> results = [];
return Row(
children: <Widget>[
LocaleBuilder(
test: 'supportedLocales',
callback: (BuildContext context) {
results.add('--- supportedLocales tests ---');
int n = 0;
for (Locale locale in AppLocalizations.supportedLocales) {
String languageCode = locale.languageCode;
String? countryCode = locale.countryCode;
String? scriptCode = locale.scriptCode;
results.add('supportedLocales[$n]: languageCode: $languageCode, countryCode: $countryCode, scriptCode: $scriptCode');
n += 1;
}
},
),
LocaleBuilder(
locale: Locale('en', 'CA'),
test: 'countryCode - en_CA',
callback: (BuildContext context) {
results.add('--- countryCode (en_CA) tests ---');
results.add(AppLocalizations.of(context)!.helloWorld);
results.add(AppLocalizations.of(context)!.hello("CA fallback World"));
},
),
LocaleBuilder(
locale: Locale('en', 'GB'),
test: 'countryCode - en_GB',
callback: (BuildContext context) {
results.add('--- countryCode (en_GB) tests ---');
results.add(AppLocalizations.of(context)!.helloWorld);
results.add(AppLocalizations.of(context)!.hello("GB fallback World"));
},
),
LocaleBuilder(
locale: Locale('zh'),
test: 'zh',
callback: (BuildContext context) {
results.add('--- zh ---');
results.add(AppLocalizations.of(context)!.helloWorld);
results.add(AppLocalizations.of(context)!.helloWorlds(0));
results.add(AppLocalizations.of(context)!.helloWorlds(1));
results.add(AppLocalizations.of(context)!.helloWorlds(2));
// Should use the fallback language, in this case,
// "Hello 世界" should be displayed.
results.add(AppLocalizations.of(context)!.hello("世界"));
// helloCost is tested in 'zh' because 'es' currency format contains a
// non-breaking space character (U+00A0), which if removed,
// makes it hard to decipher why the test is failing.
results.add(AppLocalizations.of(context)!.helloCost("价钱", 123));
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'),
test: 'zh',
callback: (BuildContext context) {
results.add('--- scriptCode: zh_Hans ---');
results.add(AppLocalizations.of(context)!.helloWorld);
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),
test: 'scriptCode - zh_Hant',
callback: (BuildContext context) {
results.add('--- scriptCode - zh_Hant ---');
results.add(AppLocalizations.of(context)!.helloWorld);
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'zh', countryCode: 'TW', scriptCode: 'Hant'),
test: 'scriptCode - zh_TW_Hant',
callback: (BuildContext context) {
results.add('--- scriptCode - zh_Hant_TW ---');
results.add(AppLocalizations.of(context)!.helloWorld);
},
),
LocaleBuilder(
locale: Locale('en'),
test: 'General formatting',
callback: (BuildContext context) {
results.add('--- General formatting tests ---');
final AppLocalizations localizations = AppLocalizations.of(context)!;
results.addAll(<String>[
'${localizations.helloWorld}',
'${localizations.helloNewlineWorld}',
'${localizations.testDollarSign}',
'${localizations.hello("World")}',
'${localizations.greeting("Hello", "World")}',
'${localizations.helloWorldOn(DateTime(1960))}',
'${localizations.helloOn("world argument", DateTime(1960), DateTime(1960))}',
'${localizations.helloWorldDuring(DateTime(1960), DateTime(2020))}',
'${localizations.helloFor(123)}',
'${localizations.helloCost("price", 123)}',
'${localizations.helloCostWithOptionalParam("price", .5)}',
'${localizations.helloCostWithSpecialCharacter1("price", .5)}',
'${localizations.helloCostWithSpecialCharacter2("price", .5)}',
'${localizations.helloCostWithSpecialCharacter3("price", .5)}',
'${localizations.helloDecimalPattern(1200000)}',
'${localizations.helloPercentPattern(1200000)}',
'${localizations.helloScientificPattern(1200000)}',
'${localizations.helloWorlds(0)}',
'${localizations.helloWorlds(1)}',
'${localizations.helloWorlds(2)}',
'${localizations.helloAdjectiveWorlds(0, "new")}',
'${localizations.helloAdjectiveWorlds(1, "new")}',
'${localizations.helloAdjectiveWorlds(2, "new")}',
'${localizations.helloWorldsOn(0, DateTime(1960))}',
'${localizations.helloWorldsOn(1, DateTime(1960))}',
'${localizations.helloWorldsOn(2, DateTime(1960))}',
'${localizations.helloWorldPopulation(0, 100)}',
'${localizations.helloWorldPopulation(1, 101)}',
'${localizations.helloWorldPopulation(2, 102)}',
'${localizations.helloWorldsInterpolation(123, "Hello", "World")}',
'${localizations.dollarSign}',
'${localizations.dollarSignPlural(1)}',
'${localizations.singleQuote}',
'${localizations.singleQuotePlural(2)}',
'${localizations.doubleQuote}',
'${localizations.doubleQuotePlural(2)}',
"${localizations.vehicleSelect('truck')}",
"${localizations.singleQuoteSelect('sedan')}",
"${localizations.doubleQuoteSelect('cabriolet')}",
"${localizations.pluralInString(1)}",
"${localizations.selectInString('he')}",
"${localizations.selectWithPlaceholder('male', 'ice cream')}",
"${localizations.selectWithPlaceholder('female', 'chocolate')}",
"${localizations.selectInPlural('male', 1)}",
"${localizations.selectInPlural('male', 2)}",
"${localizations.selectInPlural('female', 1)}",
'${localizations.datetime1(DateTime(2023, 6, 26))}',
'${localizations.datetime2(DateTime(2023, 6, 26, 5, 23))}',
]);
},
),
LocaleBuilder(
locale: Locale('es'),
test: '--- es ---',
callback: (BuildContext context) {
results.add('--- es ---');
final AppLocalizations localizations = AppLocalizations.of(context)!;
results.addAll(<String>[
'${localizations.helloWorld}',
'${localizations.helloNewlineWorld}',
'${localizations.testDollarSign}',
'${localizations.hello("Mundo")}',
'${localizations.greeting("Hola", "Mundo")}',
'${localizations.helloWorldOn(DateTime(1960))}',
'${localizations.helloOn("world argument", DateTime(1960), DateTime(1960))}',
'${localizations.helloWorldDuring(DateTime(1960), DateTime(2020))}',
'${localizations.helloFor(123)}',
// helloCost is tested in 'zh' because 'es' currency format contains a
// non-breaking space character (U+00A0), which if removed,
// makes it hard to decipher why the test is failing.
'${localizations.helloWorlds(0)}',
'${localizations.helloWorlds(1)}',
'${localizations.helloWorlds(2)}',
'${localizations.helloAdjectiveWorlds(0, "nuevo")}',
'${localizations.helloAdjectiveWorlds(1, "nuevo")}',
'${localizations.helloAdjectiveWorlds(2, "nuevo")}',
'${localizations.helloWorldsOn(0, DateTime(1960))}',
'${localizations.helloWorldsOn(1, DateTime(1960))}',
'${localizations.helloWorldsOn(2, DateTime(1960))}',
'${localizations.helloWorldPopulation(0, 100)}',
'${localizations.helloWorldPopulation(1, 101)}',
'${localizations.helloWorldPopulation(2, 102)}',
'${localizations.helloWorldsInterpolation(123, "Hola", "Mundo")}',
'${localizations.dollarSign}',
'${localizations.dollarSignPlural(1)}',
'${localizations.singleQuote}',
'${localizations.singleQuotePlural(2)}',
'${localizations.doubleQuote}',
'${localizations.doubleQuotePlural(2)}',
"${localizations.vehicleSelect('truck')}",
"${localizations.singleQuoteSelect('sedan')}",
"${localizations.doubleQuoteSelect('cabriolet')}",
"${localizations.pluralInString(1)}",
"${localizations.selectInString('he')}",
]);
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'es', countryCode: '419'),
test: 'countryCode - es_419',
callback: (BuildContext context) {
results.add('--- es_419 ---');
final AppLocalizations localizations = AppLocalizations.of(context)!;
results.addAll([
'${localizations.helloWorld}',
'${localizations.helloWorlds(0)}',
'${localizations.helloWorlds(1)}',
'${localizations.helloWorlds(2)}',
]);
},
),
LocaleBuilder(
callback: (BuildContext context) {
try {
int n = 0;
for (final String result in results) {
// Newline character replacement is necessary because
// the stream breaks up stdout by new lines.
print('#l10n $n (${result.replaceAll('\n', '_NEWLINE_')})');
n += 1;
}
}
finally {
print('#l10n END');
}
},
),
],
);
}
}
void main() {
runApp(
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Home(),
),
);
}
''';
String _getMainWithNamedParameters() => r'''
import 'package:flutter/material.dart';
import 'l10n/app_localizations.dart';
class LocaleBuilder extends StatelessWidget {
const LocaleBuilder({
Key? key,
this.locale,
this.test,
required this.callback,
}) : super(key: key);
final Locale? locale;
final String? test;
final void Function (BuildContext context) callback;
@override build(BuildContext context) {
return Localizations.override(
locale: locale,
context: context,
child: ResultBuilder(
test: test,
callback: callback,
),
);
}
}
class ResultBuilder extends StatelessWidget {
const ResultBuilder({
Key? key,
this.test,
required this.callback,
}) : super(key: key);
final String? test;
final void Function (BuildContext context) callback;
@override build(BuildContext context) {
return Builder(
builder: (BuildContext context) {
try {
callback(context);
} on Exception catch (e) {
print('#l10n A(n) $e has occurred trying to generate "$test" results.');
print('#l10n END');
}
return Container();
},
);
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
final List<String> results = [];
return Row(
children: <Widget>[
LocaleBuilder(
test: 'supportedLocales',
callback: (BuildContext context) {
results.add('--- supportedLocales tests ---');
int n = 0;
for (Locale locale in AppLocalizations.supportedLocales) {
String languageCode = locale.languageCode;
String? countryCode = locale.countryCode;
String? scriptCode = locale.scriptCode;
results.add('supportedLocales[$n]: languageCode: $languageCode, countryCode: $countryCode, scriptCode: $scriptCode');
n += 1;
}
},
),
LocaleBuilder(
locale: Locale('en', 'CA'),
test: 'countryCode - en_CA',
callback: (BuildContext context) {
results.add('--- countryCode (en_CA) tests ---');
results.add(AppLocalizations.of(context)!.helloWorld);
results.add(AppLocalizations.of(context)!.hello(world: "CA fallback World"));
},
),
LocaleBuilder(
locale: Locale('en', 'GB'),
test: 'countryCode - en_GB',
callback: (BuildContext context) {
results.add('--- countryCode (en_GB) tests ---');
results.add(AppLocalizations.of(context)!.helloWorld);
results.add(AppLocalizations.of(context)!.hello(world: "GB fallback World"));
},
),
LocaleBuilder(
locale: Locale('zh'),
test: 'zh',
callback: (BuildContext context) {
results.add('--- zh ---');
results.add(AppLocalizations.of(context)!.helloWorld);
results.add(AppLocalizations.of(context)!.helloWorlds(count: 0));
results.add(AppLocalizations.of(context)!.helloWorlds(count: 1));
results.add(AppLocalizations.of(context)!.helloWorlds(count: 2));
// Should use the fallback language, in this case,
// "Hello 世界" should be displayed.
results.add(AppLocalizations.of(context)!.hello(world: "世界"));
// helloCost is tested in 'zh' because 'es' currency format contains a
// non-breaking space character (U+00A0), which if removed,
// makes it hard to decipher why the test is failing.
results.add(AppLocalizations.of(context)!.helloCost(price: "价钱", value: 123));
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'),
test: 'zh',
callback: (BuildContext context) {
results.add('--- scriptCode: zh_Hans ---');
results.add(AppLocalizations.of(context)!.helloWorld);
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),
test: 'scriptCode - zh_Hant',
callback: (BuildContext context) {
results.add('--- scriptCode - zh_Hant ---');
results.add(AppLocalizations.of(context)!.helloWorld);
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'zh', countryCode: 'TW', scriptCode: 'Hant'),
test: 'scriptCode - zh_TW_Hant',
callback: (BuildContext context) {
results.add('--- scriptCode - zh_Hant_TW ---');
results.add(AppLocalizations.of(context)!.helloWorld);
},
),
LocaleBuilder(
locale: Locale('en'),
test: 'General formatting',
callback: (BuildContext context) {
results.add('--- General formatting tests ---');
final AppLocalizations localizations = AppLocalizations.of(context)!;
results.addAll(<String>[
'${localizations.helloWorld}',
'${localizations.helloNewlineWorld}',
'${localizations.testDollarSign}',
'${localizations.hello(world: "World")}',
'${localizations.greeting(hello: "Hello", world: "World")}',
'${localizations.helloWorldOn(date: DateTime(1960))}',
'${localizations.helloOn(world: "world argument", date: DateTime(1960), time: DateTime(1960))}',
'${localizations.helloWorldDuring(startDate: DateTime(1960), endDate: DateTime(2020))}',
'${localizations.helloFor(value: 123)}',
'${localizations.helloCost(price: "price", value: 123)}',
'${localizations.helloCostWithOptionalParam(price: "price", value: .5)}',
'${localizations.helloCostWithSpecialCharacter1(price: "price", value: .5)}',
'${localizations.helloCostWithSpecialCharacter2(price: "price", value: .5)}',
'${localizations.helloCostWithSpecialCharacter3(price: "price", value: .5)}',
'${localizations.helloDecimalPattern(value: 1200000)}',
'${localizations.helloPercentPattern(value: 1200000)}',
'${localizations.helloScientificPattern(value: 1200000)}',
'${localizations.helloWorlds(count: 0)}',
'${localizations.helloWorlds(count: 1)}',
'${localizations.helloWorlds(count: 2)}',
'${localizations.helloAdjectiveWorlds(count: 0, adjective: "new")}',
'${localizations.helloAdjectiveWorlds(count: 1, adjective: "new")}',
'${localizations.helloAdjectiveWorlds(count: 2, adjective: "new")}',
'${localizations.helloWorldsOn(count: 0, date: DateTime(1960))}',
'${localizations.helloWorldsOn(count: 1, date: DateTime(1960))}',
'${localizations.helloWorldsOn(count: 2, date: DateTime(1960))}',
'${localizations.helloWorldPopulation(count: 0, population: 100)}',
'${localizations.helloWorldPopulation(count: 1, population: 101)}',
'${localizations.helloWorldPopulation(count: 2, population: 102)}',
'${localizations.helloWorldsInterpolation(count: 123, hello: "Hello", world: "World")}',
'${localizations.dollarSign}',
'${localizations.dollarSignPlural(count: 1)}',
'${localizations.singleQuote}',
'${localizations.singleQuotePlural(count: 2)}',
'${localizations.doubleQuote}',
'${localizations.doubleQuotePlural(count: 2)}',
"${localizations.vehicleSelect(vehicleType: 'truck')}",
"${localizations.singleQuoteSelect(vehicleType: 'sedan')}",
"${localizations.doubleQuoteSelect(vehicleType: 'cabriolet')}",
"${localizations.pluralInString(count: 1)}",
"${localizations.selectInString(gender: 'he')}",
"${localizations.selectWithPlaceholder(gender: 'male', preference: 'ice cream')}",
"${localizations.selectWithPlaceholder(gender: 'female', preference: 'chocolate')}",
"${localizations.selectInPlural(gender: 'male', count: 1)}",
"${localizations.selectInPlural(gender: 'male', count: 2)}",
"${localizations.selectInPlural(gender: 'female', count: 1)}",
'${localizations.datetime1(today: DateTime(2023, 6, 26))}',
'${localizations.datetime2(current: DateTime(2023, 6, 26, 5, 23))}',
]);
},
),
LocaleBuilder(
locale: Locale('es'),
test: '--- es ---',
callback: (BuildContext context) {
results.add('--- es ---');
final AppLocalizations localizations = AppLocalizations.of(context)!;
results.addAll(<String>[
'${localizations.helloWorld}',
'${localizations.helloNewlineWorld}',
'${localizations.testDollarSign}',
'${localizations.hello(world: "Mundo")}',
'${localizations.greeting(hello: "Hola", world: "Mundo")}',
'${localizations.helloWorldOn(date: DateTime(1960))}',
'${localizations.helloOn(world: "world argument", date: DateTime(1960), time: DateTime(1960))}',
'${localizations.helloWorldDuring(startDate: DateTime(1960), endDate: DateTime(2020))}',
'${localizations.helloFor(value: 123)}',
// helloCost is tested in 'zh' because 'es' currency format contains a
// non-breaking space character (U+00A0), which if removed,
// makes it hard to decipher why the test is failing.
'${localizations.helloWorlds(count: 0)}',
'${localizations.helloWorlds(count: 1)}',
'${localizations.helloWorlds(count: 2)}',
'${localizations.helloAdjectiveWorlds(count: 0, adjective: "nuevo")}',
'${localizations.helloAdjectiveWorlds(count: 1, adjective: "nuevo")}',
'${localizations.helloAdjectiveWorlds(count: 2, adjective: "nuevo")}',
'${localizations.helloWorldsOn(count: 0, date: DateTime(1960))}',
'${localizations.helloWorldsOn(count: 1, date: DateTime(1960))}',
'${localizations.helloWorldsOn(count: 2, date: DateTime(1960))}',
'${localizations.helloWorldPopulation(count: 0, population: 100)}',
'${localizations.helloWorldPopulation(count: 1, population: 101)}',
'${localizations.helloWorldPopulation(count: 2, population: 102)}',
'${localizations.helloWorldsInterpolation(count: 123, hello: "Hola", world: "Mundo")}',
'${localizations.dollarSign}',
'${localizations.dollarSignPlural(count: 1)}',
'${localizations.singleQuote}',
'${localizations.singleQuotePlural(count: 2)}',
'${localizations.doubleQuote}',
'${localizations.doubleQuotePlural(count: 2)}',
"${localizations.vehicleSelect(vehicleType: 'truck')}",
"${localizations.singleQuoteSelect(vehicleType: 'sedan')}",
"${localizations.doubleQuoteSelect(vehicleType: 'cabriolet')}",
"${localizations.pluralInString(count: 1)}",
"${localizations.selectInString(gender: 'he')}",
]);
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'es', countryCode: '419'),
test: 'countryCode - es_419',
callback: (BuildContext context) {
results.add('--- es_419 ---');
final AppLocalizations localizations = AppLocalizations.of(context)!;
results.addAll([
'${localizations.helloWorld}',
'${localizations.helloWorlds(count: 0)}',
'${localizations.helloWorlds(count: 1)}',
'${localizations.helloWorlds(count: 2)}',
]);
},
),
LocaleBuilder(
callback: (BuildContext context) {
try {
int n = 0;
for (final String result in results) {
// Newline character replacement is necessary because
// the stream breaks up stdout by new lines.
print('#l10n $n (${result.replaceAll('\n', '_NEWLINE_')})');
n += 1;
}
}
finally {
print('#l10n END');
}
},
),
],
);
}
}
void main() {
runApp(
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Home(),
),
);
}''';
String l10nYaml({
required bool useDeferredLoading,
required bool useSyntheticPackage,
required bool useNamedParameters,
}) {
String l10nYamlString = '';
if (useDeferredLoading) {
l10nYamlString += 'use-deferred-loading: true\n';
}
if (!useSyntheticPackage) {
l10nYamlString += 'synthetic-package: false\n';
}
if (useNamedParameters) {
l10nYamlString += 'use-named-parameters: true\n';
}
return l10nYamlString;
......
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