mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[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
This commit is contained in:
parent
2edb685b23
commit
9f49181f40
@ -200,7 +200,7 @@ String generatePluralMethod(Message message, AppResourceBundle bundle) {
|
||||
'=2': 'two',
|
||||
'few': 'few',
|
||||
'many': 'many',
|
||||
'other': 'other'
|
||||
'other': 'other',
|
||||
};
|
||||
|
||||
final List<String> pluralLogicArgs = <String>[];
|
||||
@ -211,9 +211,19 @@ String generatePluralMethod(Message message, AppResourceBundle bundle) {
|
||||
String argValue = generateString(match.group(2));
|
||||
for (final Placeholder placeholder in message.placeholders) {
|
||||
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 {
|
||||
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');
|
||||
@ -238,14 +248,61 @@ String generatePluralMethod(Message message, AppResourceBundle bundle) {
|
||||
.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 generateMessage() {
|
||||
String messageValue = generateString(bundle.translationFor(message));
|
||||
for (final Placeholder placeholder in message.placeholders) {
|
||||
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 {
|
||||
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 {
|
||||
.replaceAll('@(fileName)', fileName)
|
||||
.replaceAll('@(class)', '$className${locale.camelCase()}')
|
||||
.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(
|
||||
@ -1103,9 +1161,12 @@ class LocalizationsGenerator {
|
||||
.replaceAll('@(supportedLocales)', supportedLocalesCode.join(',\n '))
|
||||
.replaceAll('@(supportedLanguageCodes)', supportedLanguageCodes.join(', '))
|
||||
.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 }) {
|
||||
// First, generate the string contents of all necessary files.
|
||||
_generateCode();
|
||||
|
@ -160,11 +160,9 @@ const String pluralMethodTemplate = '''
|
||||
|
||||
const String classFileTemplate = '''
|
||||
@(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)`).
|
||||
class @(class) extends @(baseClass) {
|
||||
|
@ -56,6 +56,9 @@ const String singleZhMessageArbFileString = '''
|
||||
{
|
||||
"title": "标题"
|
||||
}''';
|
||||
const String intlImportDartCode = '''
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
''';
|
||||
|
||||
void _standardFlutterDirectoryL10nSetup(FileSystem fs) {
|
||||
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
|
||||
});
|
||||
});
|
||||
|
||||
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(
|
||||
'should throw with descriptive error message when failing to parse the '
|
||||
'arb file',
|
||||
|
Loading…
Reference in New Issue
Block a user