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
This commit is contained in:
parent
0db0299907
commit
88cd1135bf
@ -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)
|
||||
argValue = argValue.replaceAll('#$placeholder#', '\$$placeholder');
|
||||
|
||||
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"
|
||||
}''';
|
||||
|
Loading…
x
Reference in New Issue
Block a user