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);
});
}
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