Skip to content

Commit 8be335f

Browse files
authored
Handle dollar signs properly when generating localizations (#125514)
Currently, the code doesn't properly handle strings which contain dollar signs. The return expression for the generated localization function is computed by `generateReturnExpr` which concatenates several strings, which are either interpolated placeholders, interpolated function calls, or normal strings, but we didn't properly escape dollar signs before sending normal strings to `generateReturnExpr`. Fixes #125461.
1 parent 9a28f56 commit 8be335f

File tree

3 files changed

+48
-5
lines changed

3 files changed

+48
-5
lines changed

packages/flutter_tools/lib/src/localizations/gen_l10n.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1131,7 +1131,7 @@ class LocalizationsGenerator {
11311131
case ST.message:
11321132
final List<String> expressions = node.children.map<String>((Node node) {
11331133
if (node.type == ST.string) {
1134-
return node.value!;
1134+
return generateString(node.value!);
11351135
}
11361136
return generateVariables(node);
11371137
}).toList();

packages/flutter_tools/lib/src/localizations/localizations_utils.dart

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,25 @@ String generateString(String value) {
291291
return value;
292292
}
293293

294-
/// Given a list of strings, placeholders, or helper function calls, concatenate
295-
/// them into one expression to be returned.
294+
/// Given a list of normal strings or interpolated variables, concatenate them
295+
/// into a single dart string to be returned. An example of a normal string
296+
/// would be "'Hello world!'" and an example of a interpolated variable would be
297+
/// "'$placeholder'".
296298
///
297-
/// If `isSingleStringVar` is passed, then we want to convert "'$expr'" to "expr".
299+
/// Each of the strings in [expressions] should be a raw string, which, if it
300+
/// were to be added to a dart file, would be a properly formatted dart string
301+
/// with escapes and/or interpolation. The purpose of this function is to
302+
/// concatenate these dart strings into a single dart string which can be
303+
/// returned in the generated localization files.
304+
///
305+
/// The following rules describe the kinds of string expressions that can be
306+
/// handled:
307+
/// 1. If [expressions] is empty, return the empty string "''".
308+
/// 2. If [expressions] has only one [String] which is an interpolated variable,
309+
/// it is converted to the variable itself e.g. ["'$expr'"] -> "expr".
310+
/// 3. If one string in [expressions] is an interpolation and the next begins
311+
/// with an alphanumeric character, then the former interpolation should be
312+
/// wrapped in braces e.g. ["'$expr1'", "'another'"] -> "'${expr1}another'".
298313
String generateReturnExpr(List<String> expressions, { bool isSingleStringVar = false }) {
299314
if (expressions.isEmpty) {
300315
return "''";
@@ -304,7 +319,7 @@ String generateReturnExpr(List<String> expressions, { bool isSingleStringVar = f
304319
} else {
305320
final String string = expressions.reversed.fold<String>('', (String string, String expression) {
306321
if (expression[0] != r'$') {
307-
return generateString(expression) + string;
322+
return expression + string;
308323
}
309324
final RegExp alphanumeric = RegExp(r'^([0-9a-zA-Z]|_)+$');
310325
if (alphanumeric.hasMatch(expression.substring(1)) && !(string.isNotEmpty && alphanumeric.hasMatch(string[0]))) {

packages/flutter_tools/test/general.shard/generate_localizations_test.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3244,4 +3244,32 @@ NumberFormat.decimalPatternDigits(
32443244
);
32453245
'''));
32463246
});
3247+
3248+
// Regression test for https://github.com/flutter/flutter/issues/125461.
3249+
testWithoutContext('dollar signs are escaped properly when there is a select clause', () {
3250+
const String dollarSignWithSelect = r'''
3251+
{
3252+
"dollarSignWithSelect": "$nice_bug\nHello Bug! Manifistation #1 {selectPlaceholder, select, case{message} other{messageOther}}"
3253+
}''';
3254+
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
3255+
..createSync(recursive: true);
3256+
l10nDirectory.childFile(defaultTemplateArbFileName)
3257+
.writeAsStringSync(dollarSignWithSelect);
3258+
LocalizationsGenerator(
3259+
fileSystem: fs,
3260+
inputPathString: defaultL10nPathString,
3261+
outputPathString: defaultL10nPathString,
3262+
templateArbFileName: defaultTemplateArbFileName,
3263+
outputFileString: defaultOutputFileString,
3264+
classNameString: defaultClassNameString,
3265+
logger: logger,
3266+
suppressWarnings: true,
3267+
)
3268+
..loadResources()
3269+
..writeOutputFiles();
3270+
final String localizationsFile = fs.file(
3271+
fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'),
3272+
).readAsStringSync();
3273+
expect(localizationsFile, contains(r'\$nice_bug\nHello Bug! Manifistation #1 $_temp0'));
3274+
});
32473275
}

0 commit comments

Comments
 (0)