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> ...@@ -118,4 +118,5 @@ Kim Jiun <kkimj@hanyang.ac.kr>
LinXunFeng <linxunfeng@yeah.net> LinXunFeng <linxunfeng@yeah.net>
Sabin Neupane <sabin.neupane26@gmail.com> Sabin Neupane <sabin.neupane26@gmail.com>
Mahdi Bagheri <1839491@gmail.com> Mahdi Bagheri <1839491@gmail.com>
Mok Kah Wai <taboosun1996@gmail.com>
Lucas Saudon <lsaudon@gmail.com> Lucas Saudon <lsaudon@gmail.com>
...@@ -207,6 +207,10 @@ class GenerateLocalizationsCommand extends FlutterCommand { ...@@ -207,6 +207,10 @@ class GenerateLocalizationsCommand extends FlutterCommand {
'and "}" is treated as a string if it does not close any previous "{" ' 'and "}" is treated as a string if it does not close any previous "{" '
'that is treated as a special character.', '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; final FileSystem _fileSystem;
......
...@@ -73,6 +73,7 @@ Future<LocalizationsGenerator> generateLocalizations({ ...@@ -73,6 +73,7 @@ Future<LocalizationsGenerator> generateLocalizations({
logger: logger, logger: logger,
suppressWarnings: options.suppressWarnings, suppressWarnings: options.suppressWarnings,
useRelaxedSyntax: options.relaxSyntax, useRelaxedSyntax: options.relaxSyntax,
useNamedParameters: options.useNamedParameters,
) )
..loadResources() ..loadResources()
..writeOutputFiles(isFromYaml: true, useCRLF: useCRLF); ..writeOutputFiles(isFromYaml: true, useCRLF: useCRLF);
...@@ -122,9 +123,9 @@ String _syntheticL10nPackagePath(FileSystem fileSystem) => fileSystem.path.join( ...@@ -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 // 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 // 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. // 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 message.placeholders.values.map((Placeholder placeholder) {
return '${placeholder.type} ${placeholder.name}'; return '${useNamedParameters ? 'required ' : ''}${placeholder.type} ${placeholder.name}';
}).toList(); }).toList();
} }
...@@ -231,7 +232,7 @@ Map<String, String> pluralCases = <String, String>{ ...@@ -231,7 +232,7 @@ Map<String, String> pluralCases = <String, String>{
'other': 'other', 'other': 'other',
}; };
String generateBaseClassMethod(Message message, LocaleInfo? templateArbLocale) { String generateBaseClassMethod(Message message, LocaleInfo? templateArbLocale, bool useNamedParameters) {
final String comment = message final String comment = message
.description .description
?.split('\n') ?.split('\n')
...@@ -242,11 +243,11 @@ String generateBaseClassMethod(Message message, LocaleInfo? templateArbLocale) { ...@@ -242,11 +243,11 @@ String generateBaseClassMethod(Message message, LocaleInfo? templateArbLocale) {
/// **'${generateString(message.value)}'**'''; /// **'${generateString(message.value)}'**''';
if (message.placeholders.isNotEmpty) { if (message.placeholders.isNotEmpty) {
return baseClassMethodTemplate return (useNamedParameters ? baseClassMethodWithNamedParameterTemplate : baseClassMethodTemplate)
.replaceAll('@(comment)', comment) .replaceAll('@(comment)', comment)
.replaceAll('@(templateLocaleTranslationComment)', templateLocaleTranslationComment) .replaceAll('@(templateLocaleTranslationComment)', templateLocaleTranslationComment)
.replaceAll('@(name)', message.resourceId) .replaceAll('@(name)', message.resourceId)
.replaceAll('@(parameters)', generateMethodParameters(message).join(', ')); .replaceAll('@(parameters)', generateMethodParameters(message, useNamedParameters).join(', '));
} }
return baseClassGetterTemplate return baseClassGetterTemplate
.replaceAll('@(comment)', comment) .replaceAll('@(comment)', comment)
...@@ -492,6 +493,7 @@ class LocalizationsGenerator { ...@@ -492,6 +493,7 @@ class LocalizationsGenerator {
required Logger logger, required Logger logger,
bool suppressWarnings = false, bool suppressWarnings = false,
bool useRelaxedSyntax = false, bool useRelaxedSyntax = false,
bool useNamedParameters = false,
}) { }) {
final Directory? projectDirectory = projectDirFromPath(fileSystem, projectPathString); final Directory? projectDirectory = projectDirFromPath(fileSystem, projectPathString);
final Directory inputDirectory = inputDirectoryFromPath(fileSystem, inputPathString, projectDirectory); final Directory inputDirectory = inputDirectoryFromPath(fileSystem, inputPathString, projectDirectory);
...@@ -516,6 +518,7 @@ class LocalizationsGenerator { ...@@ -516,6 +518,7 @@ class LocalizationsGenerator {
logger: logger, logger: logger,
suppressWarnings: suppressWarnings, suppressWarnings: suppressWarnings,
useRelaxedSyntax: useRelaxedSyntax, useRelaxedSyntax: useRelaxedSyntax,
useNamedParameters: useNamedParameters,
); );
} }
...@@ -541,6 +544,7 @@ class LocalizationsGenerator { ...@@ -541,6 +544,7 @@ class LocalizationsGenerator {
this.useEscaping = false, this.useEscaping = false,
this.suppressWarnings = false, this.suppressWarnings = false,
this.useRelaxedSyntax = false, this.useRelaxedSyntax = false,
this.useNamedParameters = false,
}); });
final FileSystem _fs; final FileSystem _fs;
...@@ -685,6 +689,14 @@ class LocalizationsGenerator { ...@@ -685,6 +689,14 @@ class LocalizationsGenerator {
/// Whether or not to suppress warnings or not. /// Whether or not to suppress warnings or not.
final bool suppressWarnings; 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) { static bool _isNotReadable(FileStat fileStat) {
final String rawStatString = fileStat.modeString(); final String rawStatString = fileStat.modeString();
// Removes potential prepended permission bits, such as '(suid)' and '(guid)'. // Removes potential prepended permission bits, such as '(suid)' and '(guid)'.
...@@ -1124,7 +1136,7 @@ class LocalizationsGenerator { ...@@ -1124,7 +1136,7 @@ class LocalizationsGenerator {
return fileTemplate return fileTemplate
.replaceAll('@(header)', header.isEmpty ? '' : '$header\n') .replaceAll('@(header)', header.isEmpty ? '' : '$header\n')
.replaceAll('@(class)', className) .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('@(importFile)', '$directory/$outputFileName')
.replaceAll('@(supportedLocales)', supportedLocalesCode.join(',\n ')) .replaceAll('@(supportedLocales)', supportedLocalesCode.join(',\n '))
.replaceAll('@(supportedLanguageCodes)', supportedLanguageCodes.join(', ')) .replaceAll('@(supportedLanguageCodes)', supportedLanguageCodes.join(', '))
...@@ -1306,9 +1318,9 @@ The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", " ...@@ -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 messageString = generateVariables(node, isRoot: true);
final String tempVarLines = tempVariables.isEmpty ? '' : '${tempVariables.join('\n')}\n'; final String tempVarLines = tempVariables.isEmpty ? '' : '${tempVariables.join('\n')}\n';
return methodTemplate return (useNamedParameters ? methodWithNamedParameterTemplate : methodTemplate)
.replaceAll('@(name)', message.resourceId) .replaceAll('@(name)', message.resourceId)
.replaceAll('@(parameters)', generateMethodParameters(message).join(', ')) .replaceAll('@(parameters)', generateMethodParameters(message, useNamedParameters).join(', '))
.replaceAll('@(dateFormatting)', generateDateFormattingLogic(message)) .replaceAll('@(dateFormatting)', generateDateFormattingLogic(message))
.replaceAll('@(numberFormatting)', generateNumberFormattingLogic(message)) .replaceAll('@(numberFormatting)', generateNumberFormattingLogic(message))
.replaceAll('@(tempVars)', tempVarLines) .replaceAll('@(tempVars)', tempVarLines)
......
...@@ -142,6 +142,14 @@ const String methodTemplate = ''' ...@@ -142,6 +142,14 @@ const String methodTemplate = '''
@(tempVars) return @(message); @(tempVars) return @(message);
}'''; }''';
const String methodWithNamedParameterTemplate = '''
@override
String @(name)({@(parameters)}) {
@(dateFormatting)
@(numberFormatting)
@(tempVars) return @(message);
}''';
const String pluralVariableTemplate = ''' const String pluralVariableTemplate = '''
String @(varName) = intl.Intl.pluralLogic( String @(varName) = intl.Intl.pluralLogic(
@(count), @(count),
...@@ -195,6 +203,13 @@ const String baseClassMethodTemplate = ''' ...@@ -195,6 +203,13 @@ const String baseClassMethodTemplate = '''
String @(name)(@(parameters)); String @(name)(@(parameters));
'''; ''';
const String baseClassMethodWithNamedParameterTemplate = '''
@(comment)
///
@(templateLocaleTranslationComment)
String @(name)({@(parameters)});
''';
// DELEGATE CLASS TEMPLATES // DELEGATE CLASS TEMPLATES
const String delegateClassTemplate = ''' const String delegateClassTemplate = '''
......
...@@ -355,6 +355,7 @@ class LocalizationOptions { ...@@ -355,6 +355,7 @@ class LocalizationOptions {
bool? useEscaping, bool? useEscaping,
bool? suppressWarnings, bool? suppressWarnings,
bool? relaxSyntax, bool? relaxSyntax,
bool? useNamedParameters,
}) : templateArbFile = templateArbFile ?? 'app_en.arb', }) : templateArbFile = templateArbFile ?? 'app_en.arb',
outputLocalizationFile = outputLocalizationFile ?? 'app_localizations.dart', outputLocalizationFile = outputLocalizationFile ?? 'app_localizations.dart',
outputClass = outputClass ?? 'AppLocalizations', outputClass = outputClass ?? 'AppLocalizations',
...@@ -365,7 +366,8 @@ class LocalizationOptions { ...@@ -365,7 +366,8 @@ class LocalizationOptions {
format = format ?? false, format = format ?? false,
useEscaping = useEscaping ?? false, useEscaping = useEscaping ?? false,
suppressWarnings = suppressWarnings ?? false, suppressWarnings = suppressWarnings ?? false,
relaxSyntax = relaxSyntax ?? false; relaxSyntax = relaxSyntax ?? false,
useNamedParameters = useNamedParameters ?? false;
/// The `--arb-dir` argument. /// The `--arb-dir` argument.
/// ///
...@@ -467,6 +469,14 @@ class LocalizationOptions { ...@@ -467,6 +469,14 @@ class LocalizationOptions {
/// This was added in for backward compatibility and is not recommended /// This was added in for backward compatibility and is not recommended
/// as it may mask errors. /// as it may mask errors.
final bool relaxSyntax; 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]. /// Parse the localizations configuration options from [file].
...@@ -511,6 +521,7 @@ LocalizationOptions parseLocalizationsOptionsFromYAML({ ...@@ -511,6 +521,7 @@ LocalizationOptions parseLocalizationsOptionsFromYAML({
useEscaping: _tryReadBool(yamlNode, 'use-escaping', logger), useEscaping: _tryReadBool(yamlNode, 'use-escaping', logger),
suppressWarnings: _tryReadBool(yamlNode, 'suppress-warnings', logger), suppressWarnings: _tryReadBool(yamlNode, 'suppress-warnings', logger),
relaxSyntax: _tryReadBool(yamlNode, 'relax-syntax', logger), relaxSyntax: _tryReadBool(yamlNode, 'relax-syntax', logger),
useNamedParameters: _tryReadBool(yamlNode, 'use-named-parameters', logger),
); );
} }
...@@ -537,6 +548,7 @@ LocalizationOptions parseLocalizationsOptionsFromCommand({ ...@@ -537,6 +548,7 @@ LocalizationOptions parseLocalizationsOptionsFromCommand({
format: command.boolArg('format'), format: command.boolArg('format'),
useEscaping: command.boolArg('use-escaping'), useEscaping: command.boolArg('use-escaping'),
suppressWarnings: command.boolArg('suppress-warnings'), suppressWarnings: command.boolArg('suppress-warnings'),
useNamedParameters: command.boolArg('use-named-parameters'),
); );
} }
......
...@@ -96,6 +96,7 @@ void main() { ...@@ -96,6 +96,7 @@ void main() {
bool areResourceAttributeRequired = false, bool areResourceAttributeRequired = false,
bool suppressWarnings = false, bool suppressWarnings = false,
bool relaxSyntax = false, bool relaxSyntax = false,
bool useNamedParameters = false,
void Function(Directory)? setup, void Function(Directory)? setup,
} }
) { ) {
...@@ -128,6 +129,7 @@ void main() { ...@@ -128,6 +129,7 @@ void main() {
areResourceAttributesRequired: areResourceAttributeRequired, areResourceAttributesRequired: areResourceAttributeRequired,
suppressWarnings: suppressWarnings, suppressWarnings: suppressWarnings,
useRelaxedSyntax: relaxSyntax, useRelaxedSyntax: relaxSyntax,
useNamedParameters: useNamedParameters,
) )
..loadResources() ..loadResources()
..writeOutputFiles(isFromYaml: isFromYaml); ..writeOutputFiles(isFromYaml: isFromYaml);
...@@ -2491,4 +2493,43 @@ NumberFormat.decimalPatternDigits( ...@@ -2491,4 +2493,43 @@ NumberFormat.decimalPatternDigits(
setupLocalizations(<String, String>{ 'en': dollarSignWithSelect }); setupLocalizations(<String, String>{ 'en': dollarSignWithSelect });
expect(getGeneratedFileContent(locale: 'en'), contains(r'\$nice_bug\nHello Bug! Manifistation #1 $_temp0')); 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'; ...@@ -11,7 +11,12 @@ import 'test_data/gen_l10n_project.dart';
import 'test_driver.dart'; import 'test_driver.dart';
import 'test_utils.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. // Verify that the code generated by gen_l10n executes correctly.
// It can fail if gen_l10n produces a lib/l10n/app_localizations.dart that // It can fail if gen_l10n produces a lib/l10n/app_localizations.dart that
...@@ -180,4 +185,11 @@ void main() { ...@@ -180,4 +185,11 @@ void main() {
final StringBuffer stdout = await runApp(); final StringBuffer stdout = await runApp();
expectOutput(stdout); 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