Unverified Commit 9f49181f authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

[gen-l10n] Remove need for ignoring two lints in generated code (#78778)

* Remove need for unused import for placeholder braces

* Remove need for unused intl import for when plurals aren't used in the generated code
parent 2edb685b
...@@ -200,7 +200,7 @@ String generatePluralMethod(Message message, AppResourceBundle bundle) { ...@@ -200,7 +200,7 @@ String generatePluralMethod(Message message, AppResourceBundle bundle) {
'=2': 'two', '=2': 'two',
'few': 'few', 'few': 'few',
'many': 'many', 'many': 'many',
'other': 'other' 'other': 'other',
}; };
final List<String> pluralLogicArgs = <String>[]; final List<String> pluralLogicArgs = <String>[];
...@@ -211,9 +211,19 @@ String generatePluralMethod(Message message, AppResourceBundle bundle) { ...@@ -211,9 +211,19 @@ String generatePluralMethod(Message message, AppResourceBundle bundle) {
String argValue = generateString(match.group(2)); String argValue = generateString(match.group(2));
for (final Placeholder placeholder in message.placeholders) { for (final Placeholder placeholder in message.placeholders) {
if (placeholder != countPlaceholder && placeholder.requiresFormatting) { if (placeholder != countPlaceholder && placeholder.requiresFormatting) {
argValue = argValue.replaceAll('#${placeholder.name}#', '\${${placeholder.name}String}'); argValue = argValue.replaceAll(
'#${placeholder.name}#',
_needsCurlyBracketStringInterpolation(argValue, placeholder.name)
? '\${${placeholder.name}String}'
: '\$${placeholder.name}String'
);
} else { } else {
argValue = argValue.replaceAll('#${placeholder.name}#', '\${${placeholder.name}}'); argValue = argValue.replaceAll(
'#${placeholder.name}#',
_needsCurlyBracketStringInterpolation(argValue, placeholder.name)
? '\${${placeholder.name}}'
: '\$${placeholder.name}'
);
} }
} }
pluralLogicArgs.add(' ${pluralIds[pluralKey]}: $argValue'); pluralLogicArgs.add(' ${pluralIds[pluralKey]}: $argValue');
...@@ -238,14 +248,61 @@ String generatePluralMethod(Message message, AppResourceBundle bundle) { ...@@ -238,14 +248,61 @@ String generatePluralMethod(Message message, AppResourceBundle bundle) {
.replaceAll('@(none)\n', ''); .replaceAll('@(none)\n', '');
} }
bool _needsCurlyBracketStringInterpolation(String messageString, String placeholder) {
final int placeholderIndex = messageString.indexOf(placeholder);
// This means that this message does not contain placeholders/parameters,
// since one was not found in the message.
if (placeholderIndex == -1) {
return false;
}
final bool isPlaceholderEndOfSubstring = placeholderIndex + placeholder.length + 2 == messageString.length;
if (placeholderIndex > 2 && !isPlaceholderEndOfSubstring) {
// Normal case
// Examples:
// "'The number of {hours} elapsed is: 44'" // no curly brackets.
// "'哈{hours}哈'" // no curly brackets.
// "'m#hours#m'" // curly brackets.
// "'I have to work _#hours#_' sometimes." // curly brackets.
final RegExp commonCaseRE = RegExp('[^a-zA-Z_][#{]$placeholder[#}][^a-zA-Z_]');
return !commonCaseRE.hasMatch(messageString);
} else if (placeholderIndex == 2) {
// Example:
// "'{hours} elapsed.'" // no curly brackets
// '#placeholder# ' // no curly brackets
// '#placeholder#m' // curly brackets
final RegExp startOfString = RegExp('[#{]$placeholder[#}][^a-zA-Z_]');
return !startOfString.hasMatch(messageString);
} else {
// Example:
// "'hours elapsed: {hours}'"
// "'Time elapsed: {hours}'" // no curly brackets
// ' #placeholder#' // no curly brackets
// 'm#placeholder#' // curly brackets
final RegExp endOfString = RegExp('[^a-zA-Z_][#{]$placeholder[#}]');
return !endOfString.hasMatch(messageString);
}
}
String generateMethod(Message message, AppResourceBundle bundle) { String generateMethod(Message message, AppResourceBundle bundle) {
String generateMessage() { String generateMessage() {
String messageValue = generateString(bundle.translationFor(message)); String messageValue = generateString(bundle.translationFor(message));
for (final Placeholder placeholder in message.placeholders) { for (final Placeholder placeholder in message.placeholders) {
if (placeholder.requiresFormatting) { if (placeholder.requiresFormatting) {
messageValue = messageValue.replaceAll('{${placeholder.name}}', '\${${placeholder.name}String}'); messageValue = messageValue.replaceAll(
'{${placeholder.name}}',
_needsCurlyBracketStringInterpolation(messageValue, placeholder.name)
? '\${${placeholder.name}String}'
: '\$${placeholder.name}String'
);
} else { } else {
messageValue = messageValue.replaceAll('{${placeholder.name}}', '\${${placeholder.name}}'); messageValue = messageValue.replaceAll(
'{${placeholder.name}}',
_needsCurlyBracketStringInterpolation(messageValue, placeholder.name)
? '\${${placeholder.name}}'
: '\$${placeholder.name}'
);
} }
} }
...@@ -964,7 +1021,8 @@ class LocalizationsGenerator { ...@@ -964,7 +1021,8 @@ class LocalizationsGenerator {
.replaceAll('@(fileName)', fileName) .replaceAll('@(fileName)', fileName)
.replaceAll('@(class)', '$className${locale.camelCase()}') .replaceAll('@(class)', '$className${locale.camelCase()}')
.replaceAll('@(localeName)', locale.toString()) .replaceAll('@(localeName)', locale.toString())
.replaceAll('@(methods)', methods.join('\n\n')); .replaceAll('@(methods)', methods.join('\n\n'))
.replaceAll('@(requiresIntlImport)', _containsPluralMessage() ? "import 'package:intl/intl.dart' as intl;" : '');
} }
String _generateSubclass( String _generateSubclass(
...@@ -1103,9 +1161,12 @@ class LocalizationsGenerator { ...@@ -1103,9 +1161,12 @@ class LocalizationsGenerator {
.replaceAll('@(supportedLocales)', supportedLocalesCode.join(',\n ')) .replaceAll('@(supportedLocales)', supportedLocalesCode.join(',\n '))
.replaceAll('@(supportedLanguageCodes)', supportedLanguageCodes.join(', ')) .replaceAll('@(supportedLanguageCodes)', supportedLanguageCodes.join(', '))
.replaceAll('@(messageClassImports)', sortedClassImports.join('\n')) .replaceAll('@(messageClassImports)', sortedClassImports.join('\n'))
.replaceAll('@(delegateClass)', delegateClass); .replaceAll('@(delegateClass)', delegateClass)
.replaceAll('@(requiresIntlImport)', _containsPluralMessage() ? "import 'package:intl/intl.dart' as intl;" : '');
} }
bool _containsPluralMessage() => _allMessages.any((Message message) => message.isPlural);
void writeOutputFiles(Logger logger, { bool isFromYaml = false }) { void writeOutputFiles(Logger logger, { bool isFromYaml = false }) {
// First, generate the string contents of all necessary files. // First, generate the string contents of all necessary files.
_generateCode(); _generateCode();
......
...@@ -160,11 +160,9 @@ const String pluralMethodTemplate = ''' ...@@ -160,11 +160,9 @@ const String pluralMethodTemplate = '''
const String classFileTemplate = ''' const String classFileTemplate = '''
@(header) @(header)
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import '@(fileName)';
// ignore_for_file: unnecessary_brace_in_string_interps @(requiresIntlImport)
import '@(fileName)';
/// The translations for @(language) (`@(localeName)`). /// The translations for @(language) (`@(localeName)`).
class @(class) extends @(baseClass) { class @(class) extends @(baseClass) {
......
...@@ -56,6 +56,9 @@ const String singleZhMessageArbFileString = ''' ...@@ -56,6 +56,9 @@ const String singleZhMessageArbFileString = '''
{ {
"title": "标题" "title": "标题"
}'''; }''';
const String intlImportDartCode = '''
import 'package:intl/intl.dart' as intl;
''';
void _standardFlutterDirectoryL10nSetup(FileSystem fs) { void _standardFlutterDirectoryL10nSetup(FileSystem fs) {
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
...@@ -1728,6 +1731,281 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e ...@@ -1728,6 +1731,281 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
}); });
}); });
test('intl package import should be omitted in subclass files when no plurals are included', () {
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true)
..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString)
..childFile('app_es.arb').writeAsStringSync(singleEsMessageArbFileString);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
inputPathString: defaultL10nPathString,
outputPathString: defaultL10nPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.loadResources();
generator.writeOutputFiles(BufferLogger.test());
} on Exception catch (e) {
fail('Generating output files should not fail: $e');
}
final String localizationsFile = fs.file(
fs.path.join(syntheticL10nPackagePath, 'output-localization-file_es.dart'),
).readAsStringSync();
expect(localizationsFile, isNot(contains(intlImportDartCode)));
});
test('intl package import should be kept in subclass files when plurals are included', () {
const String pluralMessageArb = '''
{
"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": "A plural message",
"placeholders": {
"count": {}
}
}
}
''';
const String pluralMessageEsArb = '''
{
"helloWorlds": "{count,plural, =0{ES - Hello} =1{ES - Hello World} =2{ES - Hello two worlds} few{ES - Hello {count} worlds} many{ES - Hello all {count} worlds} other{ES - Hello other {count} worlds}}"
}
''';
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true)
..childFile(defaultTemplateArbFileName).writeAsStringSync(pluralMessageArb)
..childFile('app_es.arb').writeAsStringSync(pluralMessageEsArb);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
inputPathString: defaultL10nPathString,
outputPathString: defaultL10nPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.loadResources();
generator.writeOutputFiles(BufferLogger.test());
} on Exception catch (e) {
fail('Generating output files should not fail: $e');
}
final String localizationsFile = fs.file(
fs.path.join(syntheticL10nPackagePath, 'output-localization-file_es.dart'),
).readAsStringSync();
expect(localizationsFile, contains(intlImportDartCode));
});
test('check for string interpolation rules', () {
const String enArbCheckList = '''
{
"one": "The number of {one} elapsed is: 44",
"@one": {
"description": "test one",
"placeholders": {
"one": {
"type": "String"
}
}
},
"two": "哈{two}哈",
"@two": {
"description": "test two",
"placeholders": {
"two": {
"type": "String"
}
}
},
"three": "m{three}m",
"@three": {
"description": "test three",
"placeholders": {
"three": {
"type": "String"
}
}
},
"four": "I have to work _{four}_ sometimes.",
"@four": {
"description": "test four",
"placeholders": {
"four": {
"type": "String"
}
}
},
"five": "{five} elapsed.",
"@five": {
"description": "test five",
"placeholders": {
"five": {
"type": "String"
}
}
},
"six": "{six}m",
"@six": {
"description": "test six",
"placeholders": {
"six": {
"type": "String"
}
}
},
"seven": "hours elapsed: {seven}",
"@seven": {
"description": "test seven",
"placeholders": {
"seven": {
"type": "String"
}
}
},
"eight": " {eight}",
"@eight": {
"description": "test eight",
"placeholders": {
"eight": {
"type": "String"
}
}
},
"nine": "m{nine}",
"@nine": {
"description": "test nine",
"placeholders": {
"nine": {
"type": "String"
}
}
}
}
''';
// It's fine that the arb is identical -- Just checking
// generated code for use of '${variable}' vs '$variable'
const String esArbCheckList = '''
{
"one": "The number of {one} elapsed is: 44",
"two": "哈{two}哈",
"three": "m{three}m",
"four": "I have to work _{four}_ sometimes.",
"five": "{five} elapsed.",
"six": "{six}m",
"seven": "hours elapsed: {seven}",
"eight": " {eight}",
"nine": "m{nine}"
}
''';
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true)
..childFile(defaultTemplateArbFileName).writeAsStringSync(enArbCheckList)
..childFile('app_es.arb').writeAsStringSync(esArbCheckList);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
inputPathString: defaultL10nPathString,
outputPathString: defaultL10nPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.loadResources();
generator.writeOutputFiles(BufferLogger.test());
} on Exception catch (e) {
if (e is L10nException) {
print(e.message);
}
fail('Generating output files should not fail: $e');
}
final String localizationsFile = fs.file(
fs.path.join(syntheticL10nPackagePath, 'output-localization-file_es.dart'),
).readAsStringSync();
expect(localizationsFile, contains(r'$one'));
expect(localizationsFile, contains(r'$two'));
expect(localizationsFile, contains(r'${three}'));
expect(localizationsFile, contains(r'${four}'));
expect(localizationsFile, contains(r'$five'));
expect(localizationsFile, contains(r'${six}m'));
expect(localizationsFile, contains(r'$seven'));
expect(localizationsFile, contains(r'$eight'));
expect(localizationsFile, contains(r'${nine}'));
});
test('check for string interpolation rules - plurals', () {
const String enArbCheckList = '''
{
"first": "{count,plural, =0{test {count} test} =1{哈{count}哈} =2{m{count}m} few{_{count}_} many{{count} test} other{{count}m}",
"@first": {
"description": "First set of plural messages to test.",
"placeholders": {
"count": {}
}
},
"second": "{count,plural, =0{test {count}} other{ {count}}",
"@second": {
"description": "Second set of plural messages to test.",
"placeholders": {
"count": {}
}
}
}
''';
// It's fine that the arb is identical -- Just checking
// generated code for use of '${variable}' vs '$variable'
const String esArbCheckList = '''
{
"first": "{count,plural, =0{test {count} test} =1{哈{count}哈} =2{m{count}m} few{_{count}_} many{{count} test} other{{count}m}",
"second": "{count,plural, =0{test {count}} other{ {count}}"
}
''';
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true)
..childFile(defaultTemplateArbFileName).writeAsStringSync(enArbCheckList)
..childFile('app_es.arb').writeAsStringSync(esArbCheckList);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
inputPathString: defaultL10nPathString,
outputPathString: defaultL10nPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.loadResources();
generator.writeOutputFiles(BufferLogger.test());
} on Exception catch (e) {
if (e is L10nException) {
print(e.message);
}
fail('Generating output files should not fail: $e');
}
final String localizationsFile = fs.file(
fs.path.join(syntheticL10nPackagePath, 'output-localization-file_es.dart'),
).readAsStringSync();
expect(localizationsFile, contains(r'test $count test'));
expect(localizationsFile, contains(r'哈$count哈'));
expect(localizationsFile, contains(r'm${count}m'));
expect(localizationsFile, contains(r'_${count}_'));
expect(localizationsFile, contains(r'$count test'));
expect(localizationsFile, contains(r'${count}m'));
expect(localizationsFile, contains(r'test $count'));
expect(localizationsFile, contains(r' $count'));
});
test( test(
'should throw with descriptive error message when failing to parse the ' 'should throw with descriptive error message when failing to parse the '
'arb file', 'arb file',
......
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