Unverified Commit 88cd1135 authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

Handle plural gen_l10n regular placeholders and DateTime placeholders (#47483)

* Handle simple placeholders in plurals

* Handle Date plural placeholders

* Improve variable names

* Turn assert into exceptions, add tests
parent 0db02999
......@@ -146,7 +146,7 @@ const String simpleMethodTemplate = '''
''';
const String pluralMethodTemplate = '''
String @methodName(@methodParameters) {
String @methodName(@methodParameters) {@dateFormatting
return Intl.plural(
@intlMethodArgs
);
......@@ -249,6 +249,22 @@ List<String> genMethodParameters(Map<String, dynamic> bundle, String key, String
return <String>[];
}
List<String> genPluralMethodParameters(Iterable<String> placeholderKeys, String countPlaceholder, String resourceId) {
if (placeholderKeys.isEmpty)
throw L10nException(
'Placeholders map for the $resourceId message is empty.\n'
'Check to see if the plural message is in the proper ICU syntax format '
'and ensure that placeholders are properly specified.'
);
return placeholderKeys.map((String parameter) {
if (parameter == countPlaceholder) {
return 'int $parameter';
}
return 'Object $parameter';
}).toList();
}
String generateDateFormattingLogic(Map<String, dynamic> bundle, String key) {
String result = '';
final Map<String, dynamic> attributesMap = bundle['@$key'] as Map<String, dynamic>;
......@@ -345,14 +361,33 @@ String genSimpleMethod(Map<String, dynamic> bundle, String key) {
.replaceAll('@intlMethodArgs', genIntlMethodArgs(bundle, key).join(',\n '));
}
String genPluralMethod(Map<String, dynamic> bundle, String key) {
final Map<String, dynamic> attributesMap = bundle['@$key'] as Map<String, dynamic>;
assert(attributesMap != null && attributesMap.containsKey('placeholders'));
final Iterable<String> placeholders = attributesMap['placeholders'].keys as Iterable<String>;
String genPluralMethod(Map<String, dynamic> arbBundle, String resourceId) {
final Map<String, dynamic> attributesMap = arbBundle['@$resourceId'] as Map<String, dynamic>;
if (attributesMap == null)
throw L10nException('Resource attribute for $resourceId does not exist.');
if (!attributesMap.containsKey('placeholders'))
throw L10nException(
'Unable to find placeholders for the plural message: $resourceId.\n'
'Check to see if the plural message is in the proper ICU syntax format '
'and ensure that placeholders are properly specified.'
);
if (attributesMap['placeholders'] is! Map<String, dynamic>)
throw L10nException(
'The "placeholders" resource attribute for the message, $resourceId, '
'is not properly formatted. Ensure that it is a map with keys that are '
'strings.'
);
final Map<String, dynamic> placeholdersMap = attributesMap['placeholders'] as Map<String, dynamic>;
final Iterable<String> placeholders = placeholdersMap.keys;
// Used to determine which placeholder is the plural count placeholder
final String resourceValue = arbBundle[resourceId] as String;
final String countPlaceholder = resourceValue.split(',')[0].substring(1);
// To make it easier to parse the plurals message, temporarily replace each
// "{placeholder}" parameter with "#placeholder#".
String message = bundle[key] as String;
String message = arbBundle[resourceId] as String;
for (String placeholder in placeholders)
message = message.replaceAll('{$placeholder}', '#$placeholder#');
......@@ -366,9 +401,9 @@ String genPluralMethod(Map<String, dynamic> bundle, String key) {
};
final List<String> methodArgs = <String>[
...placeholders,
countPlaceholder,
'locale: _localeName',
...genIntlMethodArgs(bundle, key),
...genIntlMethodArgs(arbBundle, resourceId),
];
for (String pluralKey in pluralIds.keys) {
......@@ -376,16 +411,22 @@ String genPluralMethod(Map<String, dynamic> bundle, String key) {
final RegExpMatch match = expRE.firstMatch(message);
if (match != null && match.groupCount == 2) {
String argValue = match.group(2);
for (String placeholder in placeholders)
for (String placeholder in placeholders) {
final dynamic value = placeholdersMap[placeholder];
if (value is Map<String, dynamic> && _isDateParameter(value)) {
argValue = argValue.replaceAll('#$placeholder#', '\$${placeholder}String');
} else {
argValue = argValue.replaceAll('#$placeholder#', '\$$placeholder');
}
}
methodArgs.add("${pluralIds[pluralKey]}: '$argValue'");
}
}
return pluralMethodTemplate
.replaceAll('@methodName', key)
.replaceAll('@methodParameters', genMethodParameters(bundle, key, 'int').join(', '))
.replaceAll('@methodName', resourceId)
.replaceAll('@methodParameters', genPluralMethodParameters(placeholders, countPlaceholder, resourceId).join(', '))
.replaceAll('@dateFormatting', generateDateFormattingLogic(arbBundle, resourceId))
.replaceAll('@intlMethodArgs', methodArgs.join(',\n '));
}
......
......@@ -834,6 +834,230 @@ void main() {
);
});
test('correctly generates a plural message with placeholders:', () {
const String pluralMessageWithMultiplePlaceholders = '''{
"helloWorlds": "{count,plural, =0{Hello}=1{Hello {adjective} World}=2{Hello two {adjective} worlds}few{Hello {count} {adjective} worlds}many{Hello all {count} {adjective} worlds}other{Hello other {count} {adjective} worlds}}",
"@helloWorlds": {
"placeholders": {
"count": {},
"adjective": {}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithMultiplePlaceholders);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String helloWorlds(int count, Object adjective) {
return Intl.plural(
count,
locale: _localeName,
name: 'helloWorlds',
args: <Object>[count, adjective],
zero: 'Hello',
one: 'Hello \$adjective World',
two: 'Hello two \$adjective worlds',
few: 'Hello \$count \$adjective worlds',
many: 'Hello all \$count \$adjective worlds',
other: 'Hello other \$count \$adjective worlds'
);
}
'''
);
});
test('correctly generates a plural message with DateTime placeholders:', () {
const String pluralMessageWithDateTimePlaceholder = '''{
"helloWorlds": "{count,plural, =1{Hello World, today is {currentDate}}=2{Hello two worlds, today is {currentDate}}many{Hello all {count} worlds, today is {currentDate}}other{Hello other {count} worlds, today is {currentDate}}}",
"@helloWorlds": {
"placeholders": {
"count": {},
"currentDate": {
"type": "DateTime",
"format": "yMMMMEEEEd"
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithDateTimePlaceholder);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String helloWorlds(int count, Object currentDate) {
final DateFormat currentDateDateFormat = DateFormat.yMMMMEEEEd(_localeName);
final String currentDateString = currentDateDateFormat.format(currentDate);
return Intl.plural(
count,
locale: _localeName,
name: 'helloWorlds',
args: <Object>[count, currentDateString],
one: 'Hello World, today is \$currentDateString',
two: 'Hello two worlds, today is \$currentDateString',
many: 'Hello all \$count worlds, today is \$currentDateString',
other: 'Hello other \$count worlds, today is \$currentDateString'
);
}
'''
);
});
test('should throw attempting to generate a plural message without placeholders:', () {
const String pluralMessageWithoutPlaceholdersAttribute = '''{
"helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}",
"@helloWorlds": {
"description": "Improperly formatted since it has no placeholder attribute."
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithoutPlaceholdersAttribute);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('Check to see if the plural message is in the proper ICU syntax format'));
return;
}
fail('Generating class methods without placeholders should not succeed');
});
test('should throw attempting to generate a plural message with empty placeholders map:', () {
const String pluralMessageWithEmptyPlaceholdersMap = '''{
"helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}",
"@helloWorlds": {
"description": "Improperly formatted since it has no placeholder attribute.",
"placeholders": {}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithEmptyPlaceholdersMap);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('Check to see if the plural message is in the proper ICU syntax format'));
return;
}
fail('Generating class methods without placeholders should not succeed');
});
test('should throw attempting to generate a plural message with no resource attributes:', () {
const String pluralMessageWithoutResourceAttributes = '''{
"helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}"
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithoutResourceAttributes);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('Resource attribute'));
expect(e.message, contains('does not exist'));
return;
}
fail('Generating plural class method without resource attributes should not succeed');
});
test('should throw attempting to generate a plural message with incorrect placeholders format:', () {
const String pluralMessageWithIncorrectPlaceholderFormat = '''{
"helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}",
"@helloWorlds": {
"placeholders": "Incorrectly a string, should be a map."
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithIncorrectPlaceholderFormat);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('is not properly formatted'));
expect(e.message, contains('Ensure that it is a map with keys that are strings'));
return;
}
fail('Generating class methods with incorrect placeholder format should not succeed');
});
test('should throw when failing to parse the arb file:', () {
const String arbFileWithTrailingComma = '''{
"title": "Stocks",
......@@ -867,7 +1091,7 @@ void main() {
);
});
test('should throw when resource is is missing resource attribute:', () {
test('should throw when resource is missing resource attribute:', () {
const String arbFileWithMissingResourceAttribute = '''{
"title": "Stocks"
}''';
......
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