Skip to content

Commit c147a78

Browse files
authored
fix: Select options in Flutter ARB translation languages not working when using CDN (crowdin#45)
1 parent 751d094 commit c147a78

File tree

6 files changed

+123
-25
lines changed

6 files changed

+123
-25
lines changed

example/lib/l10n/text_en.arb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,13 @@
3535
"format": "compactLong"
3636
}
3737
}
38+
},
39+
"roamingChoice": "{choice, select, month{_For 1 month} unlimited{_Unlimited} deactivate{_Deactivate} other {}}",
40+
"@roamingChoice": {
41+
"placeholders": {
42+
"choice": {
43+
"type": "String"
44+
}
45+
}
3846
}
3947
}

lib/src/common/gen_l10n_types.dart

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -367,29 +367,27 @@ class Message {
367367
placeholders = _placeholders(
368368
templateBundle.resources, resourceId, isResourceAttributeRequired),
369369
messages = <LocaleInfo, String?>{},
370-
parsedMessages = <LocaleInfo, Node?>{},
371370
_pluralMatch =
372-
_pluralRE.firstMatch(_value(templateBundle.resources, resourceId));
373-
374-
// {
375-
// // Filenames for error handling.
376-
// // final Map<LocaleInfo, String> filenames = <LocaleInfo, String>{};
377-
// // Collect all translations from allBundles and parse them.
378-
// for (final AppResourceBundle bundle in allBundles.bundles) {
379-
// // filenames[bundle.locale] = bundle.file.basename;
380-
// final String? translation = bundle.translationFor(resourceId);
381-
// messages[bundle.locale] = translation;
382-
// parsedMessages[bundle.locale] = translation == null ? null : Parser(
383-
// resourceId,
384-
// bundle.file.basename,
385-
// translation,
386-
// useEscaping: useEscaping,
387-
// // logger: logger
388-
// ).parse();
389-
// }
390-
// // Infer the placeholders
391-
// _inferPlaceholders(filenames);
392-
// }
371+
_pluralRE.firstMatch(_value(templateBundle.resources, resourceId)),
372+
parsedMessages = <LocaleInfo, Node?>{} {
373+
// Filenames for error handling.
374+
final Map<LocaleInfo, String> filenames = <LocaleInfo, String>{};
375+
// Collect all translations from allBundles and parse them.
376+
// for (final AppResourceBundle bundle in allBundles.bundles) {
377+
// filenames[bundle.locale] = bundle.file.basename;
378+
final String? translation = templateBundle.translationFor(resourceId);
379+
messages[templateBundle.locale] = translation;
380+
parsedMessages[templateBundle.locale] = translation == null
381+
? null
382+
: Parser(
383+
resourceId,
384+
'OTA data',
385+
translation,
386+
useEscaping: useEscaping,
387+
).parse();
388+
// Infer the placeholders
389+
_inferPlaceholders(filenames);
390+
}
393391

394392
final String resourceId;
395393
final String value;
@@ -506,6 +504,45 @@ class Message {
506504
}),
507505
);
508506
}
507+
508+
// Using parsed translations, attempt to infer types of placeholders used by plurals and selects.
509+
// For undeclared placeholders, create a new placeholder.
510+
void _inferPlaceholders(Map<LocaleInfo, String> filenames) {
511+
// We keep the undeclared placeholders separate so that we can sort them alphabetically afterwards.
512+
final Map<String, Placeholder> undeclaredPlaceholders =
513+
<String, Placeholder>{};
514+
// Helper for getting placeholder by name.
515+
Placeholder? getPlaceholder(String name) =>
516+
placeholders[name] ?? undeclaredPlaceholders[name];
517+
for (final LocaleInfo locale in parsedMessages.keys) {
518+
if (parsedMessages[locale] == null) {
519+
continue;
520+
}
521+
final List<Node> traversalStack = <Node>[parsedMessages[locale]!];
522+
while (traversalStack.isNotEmpty) {
523+
final Node node = traversalStack.removeLast();
524+
if (<ST>[
525+
ST.placeholderExpr,
526+
ST.pluralExpr,
527+
ST.selectExpr,
528+
].contains(node.type)) {
529+
final String identifier = node.children[1].value!;
530+
Placeholder? placeholder = getPlaceholder(identifier);
531+
if (placeholder == null) {
532+
placeholder =
533+
Placeholder(resourceId, identifier, <String, Object?>{});
534+
undeclaredPlaceholders[identifier] = placeholder;
535+
}
536+
if (node.type == ST.pluralExpr) {
537+
placeholder.isPlural = true;
538+
} else if (node.type == ST.selectExpr) {
539+
placeholder.isSelect = true;
540+
}
541+
}
542+
traversalStack.addAll(node.children);
543+
}
544+
}
545+
}
509546
}
510547

511548
// Represents the contents of one ARB file.

lib/src/crowdin_extractor.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import 'package:flutter/foundation.dart';
22
import 'package:intl/intl.dart' as intl;
3+
import 'package:crowdin_sdk/src/common/message_parser.dart';
34

45
import 'common/gen_l10n_types.dart';
6+
import 'common/localizations_utils.dart';
57

68
///finds message parameters
79
class Extractor {
@@ -49,6 +51,9 @@ class Extractor {
4951
placeholder: placeholder,
5052
placeholderValue: value,
5153
);
54+
} else if (placeholder.isSelect) {
55+
result = _findSelection(message, locale, value);
56+
return result;
5257
} else {
5358
result = value.toString();
5459
}
@@ -57,6 +62,20 @@ class Extractor {
5762
return buffer;
5863
}
5964

65+
String _findSelection(Message message, String locale, String select) {
66+
final node =
67+
message.parsedMessages[LocaleInfo.fromString(locale)]?.children[0];
68+
69+
final Node selectParts = node!.children[5];
70+
final Node selectedPart = selectParts.children.firstWhere(
71+
(element) => element.children[0].value == select,
72+
orElse: () => selectParts.children
73+
.firstWhere((element) => element.children[0].value == 'other'));
74+
final String selectedValue =
75+
selectedPart.children[2].children[0].value ?? 'other';
76+
return selectedValue;
77+
}
78+
6079
String _findNumberPlaceholder({
6180
required Map<String, dynamic> optionals,
6281
required Placeholder placeholder,

test/crowdin_extractor_test.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,25 @@ void main() {
118118
expect(result, "Counter: $counterValue");
119119
});
120120
});
121+
122+
group('CrowdinFormatter select tests', () {
123+
Extractor extractor = Extractor();
124+
Message message = Message(AppResourceBundle(testArb), 'select_test', false);
125+
126+
test('should return proper values for selections', () {
127+
var result1 = extractor
128+
.findPlaceholders('en', message, message.value, {'choice': 'first'});
129+
expect(result1, 'First selection');
130+
131+
var result2 = extractor
132+
.findPlaceholders('en', message, message.value, {'choice': 'second'});
133+
expect(result2, 'Second selection');
134+
});
135+
136+
test('should return "other" value for not existing selection', () {
137+
var result = extractor.findPlaceholders(
138+
'en', message, message.value, {'choice': 'not existing choice'});
139+
expect(result, 'No selection chosen');
140+
});
141+
});
121142
}

test/crowdin_generator_test.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@ void main() {
1010
group('findPlaceholders test', () {
1111
test('should return list of translation keys', () {
1212
var keys = CrowdinGenerator.getKeys(testArb);
13-
expect(
14-
keys, ['example', 'hello', 'nThings', 'variable_nThings', 'counter']);
13+
expect(keys, [
14+
'example',
15+
'hello',
16+
'nThings',
17+
'variable_nThings',
18+
'counter',
19+
'select_test',
20+
]);
1521
});
1622

1723
test('should return list of method parameters', () {

test/test_arb.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ var testArb = {
3232
"placeholders": {
3333
"value": {"type": "int", "format": "compactLong"}
3434
}
35+
},
36+
"select_test":
37+
"{choice, select, first{First selection} second{Second selection} other{No selection chosen}}",
38+
"@select_test": {
39+
"placeholders": {
40+
"choice": {"type": "String"}
41+
}
3542
}
3643
};
3744

@@ -60,5 +67,5 @@ var testPreviewArb = {
6067
"placeholders": {
6168
"value": {"type": "int", "format": "compactLong"}
6269
}
63-
}
70+
},
6471
};

0 commit comments

Comments
 (0)