Unverified Commit 215f6372 authored by Tae Hyung Kim's avatar Tae Hyung Kim Committed by GitHub

Refactor Message class to hold all translations (#115506)

* init

* more fixing

* finish

* fix lint

* address pr comments

* redo checks
parent 4a5dd9c9
...@@ -92,14 +92,14 @@ String _syntheticL10nPackagePath(FileSystem fileSystem) => fileSystem.path.join( ...@@ -92,14 +92,14 @@ String _syntheticL10nPackagePath(FileSystem fileSystem) => fileSystem.path.join(
// TODO(thkim1011): Let's store the output of this function in the Message class, so that we don't // TODO(thkim1011): Let's store the output of this function in the Message class, so that we don't
// recompute this. See https://github.com/flutter/flutter/issues/112709 // recompute this. See https://github.com/flutter/flutter/issues/112709
List<String> generateMethodParameters(Message message) { List<String> generateMethodParameters(Message message) {
return message.placeholders.map((Placeholder placeholder) { return message.placeholders.values.map((Placeholder placeholder) {
return '${placeholder.type} ${placeholder.name}'; return '${placeholder.type} ${placeholder.name}';
}).toList(); }).toList();
} }
// Similar to above, but is used for passing arguments into helper functions. // Similar to above, but is used for passing arguments into helper functions.
List<String> generateMethodArguments(Message message) { List<String> generateMethodArguments(Message message) {
return message.placeholders.map((Placeholder placeholder) => placeholder.name).toList(); return message.placeholders.values.map((Placeholder placeholder) => placeholder.name).toList();
} }
String generateDateFormattingLogic(Message message) { String generateDateFormattingLogic(Message message) {
...@@ -107,7 +107,7 @@ String generateDateFormattingLogic(Message message) { ...@@ -107,7 +107,7 @@ String generateDateFormattingLogic(Message message) {
return '@(none)'; return '@(none)';
} }
final Iterable<String> formatStatements = message.placeholders final Iterable<String> formatStatements = message.placeholders.values
.where((Placeholder placeholder) => placeholder.requiresDateFormatting) .where((Placeholder placeholder) => placeholder.requiresDateFormatting)
.map((Placeholder placeholder) { .map((Placeholder placeholder) {
final String? placeholderFormat = placeholder.format; final String? placeholderFormat = placeholder.format;
...@@ -150,7 +150,7 @@ String generateNumberFormattingLogic(Message message) { ...@@ -150,7 +150,7 @@ String generateNumberFormattingLogic(Message message) {
return '@(none)'; return '@(none)';
} }
final Iterable<String> formatStatements = message.placeholders final Iterable<String> formatStatements = message.placeholders.values
.where((Placeholder placeholder) => placeholder.requiresNumFormatting) .where((Placeholder placeholder) => placeholder.requiresNumFormatting)
.map((Placeholder placeholder) { .map((Placeholder placeholder) {
final String? placeholderFormat = placeholder.format; final String? placeholderFormat = placeholder.format;
...@@ -502,8 +502,10 @@ class LocalizationsGenerator { ...@@ -502,8 +502,10 @@ class LocalizationsGenerator {
final FileSystem _fs; final FileSystem _fs;
Iterable<Message> _allMessages = <Message>[]; Iterable<Message> _allMessages = <Message>[];
late final AppResourceBundleCollection _allBundles = AppResourceBundleCollection(inputDirectory); late final AppResourceBundleCollection _allBundles = AppResourceBundleCollection(inputDirectory);
late final AppResourceBundle _templateBundle = AppResourceBundle(templateArbFile); late final AppResourceBundle _templateBundle = AppResourceBundle(templateArbFile);
late final Map<LocaleInfo, String> _inputFileNames = Map<LocaleInfo, String>.fromEntries(
_allBundles.bundles.map((AppResourceBundle bundle) => MapEntry<LocaleInfo, String>(bundle.locale, bundle.file.basename))
);
late final LocaleInfo _templateArbLocale = _templateBundle.locale; late final LocaleInfo _templateArbLocale = _templateBundle.locale;
@visibleForTesting @visibleForTesting
...@@ -843,7 +845,7 @@ class LocalizationsGenerator { ...@@ -843,7 +845,7 @@ class LocalizationsGenerator {
// files in inputDirectory. Also initialized: supportedLocales. // files in inputDirectory. Also initialized: supportedLocales.
void loadResources() { void loadResources() {
_allMessages = _templateBundle.resourceIds.map((String id) => Message( _allMessages = _templateBundle.resourceIds.map((String id) => Message(
_templateBundle.resources, id, areResourceAttributesRequired, _templateBundle, _allBundles, id, areResourceAttributesRequired,
)); ));
for (final String resourceId in _templateBundle.resourceIds) { for (final String resourceId in _templateBundle.resourceIds) {
if (!_isValidGetterAndMethodName(resourceId)) { if (!_isValidGetterAndMethodName(resourceId)) {
...@@ -891,21 +893,19 @@ class LocalizationsGenerator { ...@@ -891,21 +893,19 @@ class LocalizationsGenerator {
String className, String className,
String fileName, String fileName,
String header, String header,
AppResourceBundle bundle, final LocaleInfo locale,
AppResourceBundle templateBundle,
Iterable<Message> messages,
) { ) {
final LocaleInfo locale = bundle.locale; final Iterable<String> methods = _allMessages.map((Message message) {
if (message.messages[locale] == null) {
final Iterable<String> methods = messages.map((Message message) {
if (bundle.translationFor(message) == null) {
_addUnimplementedMessage(locale, message.resourceId); _addUnimplementedMessage(locale, message.resourceId);
return _generateMethod(
message,
_templateArbLocale,
);
} }
return _generateMethod( return _generateMethod(
message, message,
bundle.file.basename, locale,
bundle.translationFor(message) ?? templateBundle.translationFor(message)!,
); );
}); });
...@@ -923,20 +923,19 @@ class LocalizationsGenerator { ...@@ -923,20 +923,19 @@ class LocalizationsGenerator {
String _generateSubclass( String _generateSubclass(
String className, String className,
AppResourceBundle bundle, AppResourceBundle bundle,
Iterable<Message> messages,
) { ) {
final LocaleInfo locale = bundle.locale; final LocaleInfo locale = bundle.locale;
final String baseClassName = '$className${LocaleInfo.fromString(locale.languageCode).camelCase()}'; final String baseClassName = '$className${LocaleInfo.fromString(locale.languageCode).camelCase()}';
messages _allMessages
.where((Message message) => bundle.translationFor(message) == null) .where((Message message) => message.messages[locale] == null)
.forEach((Message message) { .forEach((Message message) {
_addUnimplementedMessage(locale, message.resourceId); _addUnimplementedMessage(locale, message.resourceId);
}); });
final Iterable<String> methods = messages final Iterable<String> methods = _allMessages
.where((Message message) => bundle.translationFor(message) != null) .where((Message message) => message.messages[locale] != null)
.map((Message message) => _generateMethod(message, bundle.file.basename, bundle.translationFor(message)!)); .map((Message message) => _generateMethod(message, locale));
return subclassTemplate return subclassTemplate
.replaceAll('@(language)', describeLocale(locale.toString())) .replaceAll('@(language)', describeLocale(locale.toString()))
...@@ -1016,9 +1015,7 @@ class LocalizationsGenerator { ...@@ -1016,9 +1015,7 @@ class LocalizationsGenerator {
className, className,
outputFileName, outputFileName,
header, header,
_allBundles.bundleFor(locale)!, locale,
_allBundles.bundleFor(_templateArbLocale)!,
_allMessages,
); );
// Every locale for the language except the base class. // Every locale for the language except the base class.
...@@ -1029,7 +1026,6 @@ class LocalizationsGenerator { ...@@ -1029,7 +1026,6 @@ class LocalizationsGenerator {
return _generateSubclass( return _generateSubclass(
className, className,
_allBundles.bundleFor(locale)!, _allBundles.bundleFor(locale)!,
_allMessages,
); );
}); });
...@@ -1079,13 +1075,14 @@ class LocalizationsGenerator { ...@@ -1079,13 +1075,14 @@ class LocalizationsGenerator {
.replaceAll('\n\n\n', '\n\n'); .replaceAll('\n\n\n', '\n\n');
} }
String _generateMethod(Message message, String filename, String translationForMessage) { String _generateMethod(Message message, LocaleInfo locale) {
// Determine if we must import intl for date or number formatting. // Determine if we must import intl for date or number formatting.
if (message.placeholdersRequireFormatting) { if (message.placeholdersRequireFormatting) {
requiresIntlImport = true; requiresIntlImport = true;
} }
final Node node = Parser(message.resourceId, filename, translationForMessage).parse(); final String translationForMessage = message.messages[locale]!;
final Node node = message.parsedMessages[locale]!;
// If parse tree is only a string, then return a getter method. // If parse tree is only a string, then return a getter method.
if (node.children.every((Node child) => child.type == ST.string)) { if (node.children.every((Node child) => child.type == ST.string)) {
// Use the parsed translation to handle escaping with the same behavior. // Use the parsed translation to handle escaping with the same behavior.
...@@ -1150,17 +1147,16 @@ class LocalizationsGenerator { ...@@ -1150,17 +1147,16 @@ class LocalizationsGenerator {
assert(node.children[1].type == ST.identifier); assert(node.children[1].type == ST.identifier);
final Node identifier = node.children[1]; final Node identifier = node.children[1];
// Check that placeholders exist. // Check that placeholders exist.
// TODO(thkim1011): Make message.placeholders a map so that we don't need to do linear time search. final Placeholder? placeholder = message.placeholders[identifier.value];
// See https://github.com/flutter/flutter/issues/112709 if (placeholder == null) {
final Placeholder placeholder = message.placeholders.firstWhere( throw L10nParserException(
(Placeholder placeholder) => placeholder.name == identifier.value, 'Make sure that the specified placeholder is defined in your arb file.',
orElse: () { _inputFileNames[locale]!,
throw L10nException(''' message.resourceId,
Make sure that the specified placeholder is defined in your arb file. translationForMessage,
$translationForMessage identifier.positionInMessage,
${Parser.indentForError(identifier.positionInMessage)}'''); );
} }
);
dependentPlaceholders.add(placeholder); dependentPlaceholders.add(placeholder);
return HelperMethod(dependentPlaceholders, placeholder: placeholder); return HelperMethod(dependentPlaceholders, placeholder: placeholder);
...@@ -1175,25 +1171,27 @@ ${Parser.indentForError(identifier.positionInMessage)}'''); ...@@ -1175,25 +1171,27 @@ ${Parser.indentForError(identifier.positionInMessage)}''');
final Node identifier = node.children[1]; final Node identifier = node.children[1];
final Node pluralParts = node.children[5]; final Node pluralParts = node.children[5];
// Check that identifier exists and is of type int or num. // Check that placeholders exist and is of type int or num.
final Placeholder placeholder = message.placeholders.firstWhere( final Placeholder? placeholder = message.placeholders[identifier.value];
(Placeholder placeholder) => placeholder.name == identifier.value, if (placeholder == null) {
orElse: () { throw L10nParserException(
throw L10nException(''' 'Make sure that the specified placeholder is defined in your arb file.',
Make sure that the specified plural placeholder is defined in your arb file. _inputFileNames[locale]!,
$translationForMessage message.resourceId,
${List<String>.filled(identifier.positionInMessage, ' ').join()}^'''); translationForMessage,
} identifier.positionInMessage,
); );
}
if (placeholder.type != 'num' && placeholder.type != 'int') {
throw L10nParserException(
'The specified placeholder must be of type int or num.',
_inputFileNames[locale]!,
message.resourceId,
translationForMessage,
identifier.positionInMessage,
);
}
dependentPlaceholders.add(placeholder); dependentPlaceholders.add(placeholder);
// TODO(thkim1011): Uncomment the following lines after Message refactor.
// See https://github.com/flutter/flutter/issues/112709.
// if (placeholder.type != 'num' && placeholder.type != 'int') {
// throw L10nException('''
// The specified placeholder must be of type int or num.
// $translationForMessage
// ${List<String>.filled(identifier.positionInMessage, ' ').join()}^''');
// }
for (final Node pluralPart in pluralParts.children.reversed) { for (final Node pluralPart in pluralParts.children.reversed) {
String pluralCase; String pluralCase;
...@@ -1239,16 +1237,17 @@ ${Parser.indentForError(pluralPart.positionInMessage)} ...@@ -1239,16 +1237,17 @@ ${Parser.indentForError(pluralPart.positionInMessage)}
assert(node.children[5].type == ST.selectParts); assert(node.children[5].type == ST.selectParts);
final Node identifier = node.children[1]; final Node identifier = node.children[1];
// Check that identifier exists // Check that placeholders exist.
final Placeholder placeholder = message.placeholders.firstWhere( final Placeholder? placeholder = message.placeholders[identifier.value];
(Placeholder placeholder) => placeholder.name == identifier.value, if (placeholder == null) {
orElse: () { throw L10nParserException(
throw L10nException(''' 'Make sure that the specified placeholder is defined in your arb file.',
Make sure that the specified select placeholder is defined in your arb file. _inputFileNames[locale]!,
$translationForMessage message.resourceId,
${Parser.indentForError(identifier.positionInMessage)}'''); translationForMessage,
} identifier.positionInMessage,
); );
}
dependentPlaceholders.add(placeholder); dependentPlaceholders.add(placeholder);
final List<String> selectLogicArgs = <String>[]; final List<String> selectLogicArgs = <String>[];
final Node selectParts = node.children[5]; final Node selectParts = node.children[5];
......
...@@ -7,6 +7,7 @@ import 'package:intl/locale.dart'; ...@@ -7,6 +7,7 @@ import 'package:intl/locale.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../convert.dart'; import '../convert.dart';
import 'localizations_utils.dart'; import 'localizations_utils.dart';
import 'message_parser.dart';
// The set of date formats that can be automatically localized. // The set of date formats that can be automatically localized.
// //
...@@ -213,7 +214,7 @@ class Placeholder { ...@@ -213,7 +214,7 @@ class Placeholder {
: assert(resourceId != null), : assert(resourceId != null),
assert(name != null), assert(name != null),
example = _stringAttribute(resourceId, name, attributes, 'example'), example = _stringAttribute(resourceId, name, attributes, 'example'),
type = _stringAttribute(resourceId, name, attributes, 'type') ?? 'Object', type = _stringAttribute(resourceId, name, attributes, 'type'),
format = _stringAttribute(resourceId, name, attributes, 'format'), format = _stringAttribute(resourceId, name, attributes, 'format'),
optionalParameters = _optionalParameters(resourceId, name, attributes), optionalParameters = _optionalParameters(resourceId, name, attributes),
isCustomDateFormat = _boolAttribute(resourceId, name, attributes, 'isCustomDateFormat'); isCustomDateFormat = _boolAttribute(resourceId, name, attributes, 'isCustomDateFormat');
...@@ -221,10 +222,13 @@ class Placeholder { ...@@ -221,10 +222,13 @@ class Placeholder {
final String resourceId; final String resourceId;
final String name; final String name;
final String? example; final String? example;
String? type;
final String? format; final String? format;
final List<OptionalParameter> optionalParameters; final List<OptionalParameter> optionalParameters;
final bool? isCustomDateFormat; final bool? isCustomDateFormat;
// The following will be initialized after all messages are parsed in the Message constructor.
String? type;
bool isPlural = false;
bool isSelect = false;
bool get requiresFormatting => requiresDateFormatting || requiresNumFormatting; bool get requiresFormatting => requiresDateFormatting || requiresNumFormatting;
bool get requiresDateFormatting => type == 'DateTime'; bool get requiresDateFormatting => type == 'DateTime';
...@@ -294,7 +298,7 @@ class Placeholder { ...@@ -294,7 +298,7 @@ class Placeholder {
} }
} }
// One translation: one pair of foo,@foo entries from the template ARB file. // All translations for a given message specified by a resource id.
// //
// The template ARB file must contain an entry called @myResourceId for each // The template ARB file must contain an entry called @myResourceId for each
// message named myResourceId. The @ entry describes message parameters // message named myResourceId. The @ entry describes message parameters
...@@ -309,48 +313,95 @@ class Placeholder { ...@@ -309,48 +313,95 @@ class Placeholder {
// The value of this Message is "Hello World". The Message's value is the // The value of this Message is "Hello World". The Message's value is the
// localized string to be shown for the template ARB file's locale. // localized string to be shown for the template ARB file's locale.
// The docs for the Placeholder explain how placeholder entries are defined. // The docs for the Placeholder explain how placeholder entries are defined.
// TODO(thkim1011): We need to refactor this Message class to own all the messages in each language.
// See https://github.com/flutter/flutter/issues/112709.
class Message { class Message {
Message(Map<String, Object?> bundle, this.resourceId, bool isResourceAttributeRequired) Message(
: assert(bundle != null), AppResourceBundle templateBundle,
AppResourceBundleCollection allBundles,
this.resourceId,
bool isResourceAttributeRequired
) : assert(templateBundle != null),
assert(allBundles != null),
assert(resourceId != null && resourceId.isNotEmpty), assert(resourceId != null && resourceId.isNotEmpty),
value = _value(bundle, resourceId), value = _value(templateBundle.resources, resourceId),
description = _description(bundle, resourceId, isResourceAttributeRequired), description = _description(templateBundle.resources, resourceId, isResourceAttributeRequired),
placeholders = _placeholders(bundle, resourceId, isResourceAttributeRequired), placeholders = _placeholders(templateBundle.resources, resourceId, isResourceAttributeRequired),
_pluralMatch = _pluralRE.firstMatch(_value(bundle, resourceId)), messages = <LocaleInfo, String?>{},
_selectMatch = _selectRE.firstMatch(_value(bundle, resourceId)) { parsedMessages = <LocaleInfo, Node?>{} {
if (isPlural) { // Filenames for error handling.
final Placeholder placeholder = getCountPlaceholder(); final Map<LocaleInfo, String> filenames = <LocaleInfo, String>{};
// Collect all translations from allBundles and parse them.
for (final AppResourceBundle bundle in allBundles.bundles) {
filenames[bundle.locale] = bundle.file.basename;
final String? translation = bundle.translationFor(resourceId);
messages[bundle.locale] = translation;
parsedMessages[bundle.locale] = translation == null ? null : Parser(resourceId, bundle.file.basename, translation).parse();
}
// Using parsed translations, attempt to infer types of placeholders used by plurals and selects.
for (final LocaleInfo locale in parsedMessages.keys) {
if (parsedMessages[locale] == null) {
continue;
}
final List<Node> traversalStack = <Node>[parsedMessages[locale]!];
while (traversalStack.isNotEmpty) {
final Node node = traversalStack.removeLast();
if (node.type == ST.pluralExpr) {
final Placeholder? placeholder = placeholders[node.children[1].value!];
if (placeholder == null) {
throw L10nParserException(
'Make sure that the specified plural placeholder is defined in your arb file.',
filenames[locale]!,
resourceId,
messages[locale]!,
node.children[1].positionInMessage
);
}
placeholders[node.children[1].value!]!.isPlural = true;
}
if (node.type == ST.selectExpr) {
final Placeholder? placeholder = placeholders[node.children[1].value!];
if (placeholder == null) {
throw L10nParserException(
'Make sure that the specified select placeholder is defined in your arb file.',
filenames[locale]!,
resourceId,
messages[locale]!,
node.children[1].positionInMessage
);
}
placeholders[node.children[1].value!]!.isSelect = true;
}
traversalStack.addAll(node.children);
}
}
for (final Placeholder placeholder in placeholders.values) {
if (placeholder.isPlural && placeholder.isSelect) {
throw L10nException('Placeholder is used as both a plural and select in certain languages.');
} else if (placeholder.isPlural) {
if (placeholder.type == null) {
placeholder.type = 'num'; placeholder.type = 'num';
} }
else if (!<String>['num', 'int'].contains(placeholder.type)) {
throw L10nException("Placeholders used in plurals must be of type 'num' or 'int'");
}
} else if (placeholder.isSelect) {
if (placeholder.type == null) {
placeholder.type = 'String';
} else if (placeholder.type != 'String') {
throw L10nException("Placeholders used in selects must be of type 'String'");
}
} }
placeholder.type ??= 'Object';
static final RegExp _pluralRE = RegExp(r'\s*\{([\w\s,]*),\s*plural\s*,'); }
static final RegExp _selectRE = RegExp(r'\s*\{([\w\s,]*),\s*select\s*,'); }
final String resourceId; final String resourceId;
final String value; final String value;
final String? description; final String? description;
final List<Placeholder> placeholders; late final Map<LocaleInfo, String?> messages;
final RegExpMatch? _pluralMatch; final Map<LocaleInfo, Node?> parsedMessages;
final RegExpMatch? _selectMatch; final Map<String, Placeholder> placeholders;
bool get isPlural => _pluralMatch != null && _pluralMatch!.groupCount == 1; bool get placeholdersRequireFormatting => placeholders.values.any((Placeholder p) => p.requiresFormatting);
bool get isSelect => _selectMatch != null && _selectMatch!.groupCount == 1;
bool get placeholdersRequireFormatting => placeholders.any((Placeholder p) => p.requiresFormatting);
Placeholder getCountPlaceholder() {
assert(isPlural);
final String countPlaceholderName = _pluralMatch![1]!;
return placeholders.firstWhere(
(Placeholder p) => p.name == countPlaceholderName,
orElse: () {
throw L10nException('Cannot find the $countPlaceholderName placeholder in plural message "$resourceId".');
}
);
}
static String _value(Map<String, Object?> bundle, String resourceId) { static String _value(Map<String, Object?> bundle, String resourceId) {
final Object? value = bundle[resourceId]; final Object? value = bundle[resourceId];
...@@ -385,23 +436,6 @@ class Message { ...@@ -385,23 +436,6 @@ class Message {
); );
} }
if (attributes == null) {
void throwEmptyAttributes(final RegExp regExp, final String type) {
final RegExpMatch? match = regExp.firstMatch(_value(bundle, resourceId));
final bool isMatch = match != null && match.groupCount == 1;
if (isMatch) {
throw L10nException(
'Resource attribute "@$resourceId" was not found. Please '
'ensure that $type resources have a corresponding @resource.'
);
}
}
throwEmptyAttributes(_pluralRE, 'plural');
throwEmptyAttributes(_selectRE, 'select');
}
return attributes as Map<String, Object?>?; return attributes as Map<String, Object?>?;
} }
...@@ -427,18 +461,18 @@ class Message { ...@@ -427,18 +461,18 @@ class Message {
return value; return value;
} }
static List<Placeholder> _placeholders( static Map<String, Placeholder> _placeholders(
Map<String, Object?> bundle, Map<String, Object?> bundle,
String resourceId, String resourceId,
bool isResourceAttributeRequired, bool isResourceAttributeRequired,
) { ) {
final Map<String, Object?>? resourceAttributes = _attributes(bundle, resourceId, isResourceAttributeRequired); final Map<String, Object?>? resourceAttributes = _attributes(bundle, resourceId, isResourceAttributeRequired);
if (resourceAttributes == null) { if (resourceAttributes == null) {
return <Placeholder>[]; return <String, Placeholder>{};
} }
final Object? allPlaceholdersMap = resourceAttributes['placeholders']; final Object? allPlaceholdersMap = resourceAttributes['placeholders'];
if (allPlaceholdersMap == null) { if (allPlaceholdersMap == null) {
return <Placeholder>[]; return <String, Placeholder>{};
} }
if (allPlaceholdersMap is! Map<String, Object?>) { if (allPlaceholdersMap is! Map<String, Object?>) {
throw L10nException( throw L10nException(
...@@ -446,17 +480,19 @@ class Message { ...@@ -446,17 +480,19 @@ class Message {
'properly formatted. Ensure that it is a map with string valued keys.' 'properly formatted. Ensure that it is a map with string valued keys.'
); );
} }
return allPlaceholdersMap.keys.map<Placeholder>((String placeholderName) { return Map<String, Placeholder>.fromEntries(
final Object? value = allPlaceholdersMap[placeholderName]; allPlaceholdersMap.keys.map((String placeholderName) {
if (value is! Map<String, Object?>) { final Object? value = allPlaceholdersMap[placeholderName];
throw L10nException( if (value is! Map<String, Object?>) {
'The value of the "$placeholderName" placeholder attribute for message ' throw L10nException(
'"$resourceId", is not properly formatted. Ensure that it is a map ' 'The value of the "$placeholderName" placeholder attribute for message '
'with string valued keys.' '"$resourceId", is not properly formatted. Ensure that it is a map '
); 'with string valued keys.'
} );
return Placeholder(resourceId, placeholderName, value); }
}).toList(); return MapEntry<String, Placeholder>(placeholderName, Placeholder(resourceId, placeholderName, value));
}),
);
} }
} }
...@@ -532,10 +568,11 @@ class AppResourceBundle { ...@@ -532,10 +568,11 @@ class AppResourceBundle {
final File file; final File file;
final LocaleInfo locale; final LocaleInfo locale;
/// JSON representation of the contents of the ARB file.
final Map<String, Object?> resources; final Map<String, Object?> resources;
final Iterable<String> resourceIds; final Iterable<String> resourceIds;
String? translationFor(Message message) => resources[message.resourceId] as String?; String? translationFor(String resourceId) => resources[resourceId] as String?;
@override @override
String toString() { String toString() {
......
...@@ -1618,8 +1618,8 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e ...@@ -1618,8 +1618,8 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
'message', 'message',
contains(''' contains('''
Make sure that the specified placeholder is defined in your arb file. Make sure that the specified placeholder is defined in your arb file.
Hello {name} [app_en.arb:helloWorld] Hello {name}
^'''), ^'''),
)), )),
); );
}); });
...@@ -1940,13 +1940,10 @@ Hello {name} ...@@ -1940,13 +1940,10 @@ Hello {name}
throwsA(isA<L10nException>().having( throwsA(isA<L10nException>().having(
(L10nException e) => e.message, (L10nException e) => e.message,
'message', 'message',
// TODO(thkim1011): Uncomment after work on refactoring the Message class. contains('''
// See https://github.com/flutter/flutter/issues/112709. Make sure that the specified plural placeholder is defined in your arb file.
// contains(''' [app_en.arb: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}}
// Make sure that the specified plural placeholder is defined in your arb file. ^'''),
// {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}}
// ^'''),
contains('Cannot find the count placeholder in plural message "helloWorlds".'),
)), )),
); );
}); });
...@@ -1983,13 +1980,10 @@ Hello {name} ...@@ -1983,13 +1980,10 @@ Hello {name}
throwsA(isA<L10nException>().having( throwsA(isA<L10nException>().having(
(L10nException e) => e.message, (L10nException e) => e.message,
'message', 'message',
// TODO(thkim1011): Uncomment after work on refactoring the Message class. contains('''
// See https://github.com/flutter/flutter/issues/112709. Make sure that the specified plural placeholder is defined in your arb file.
// contains(''' [app_en.arb: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}}
// Make sure that the specified plural placeholder is defined in your arb file. ^'''),
// {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}}
// ^'''),
contains('Cannot find the count placeholder in plural message "helloWorlds".'),
)), )),
); );
}); });
...@@ -2022,13 +2016,10 @@ Hello {name} ...@@ -2022,13 +2016,10 @@ Hello {name}
throwsA(isA<L10nException>().having( throwsA(isA<L10nException>().having(
(L10nException e) => e.message, (L10nException e) => e.message,
'message', 'message',
// TODO(thkim1011): Uncomment after work on refactoring the Message class. contains('''
// See https://github.com/flutter/flutter/issues/112709. Make sure that the specified plural placeholder is defined in your arb file.
// contains(''' [app_en.arb: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}}
// Make sure that the specified plural placeholder is defined in your arb file. ^'''),
// {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}}
// ^'''),
contains('Resource attribute "@helloWorlds" was not found. Please ensure that plural resources have a corresponding @resource.'),
)), )),
); );
}); });
...@@ -2107,8 +2098,8 @@ Hello {name} ...@@ -2107,8 +2098,8 @@ Hello {name}
'message', 'message',
contains(''' contains('''
Make sure that the specified select placeholder is defined in your arb file. Make sure that the specified select placeholder is defined in your arb file.
{gender, select, female {She} male {He} other {they} } [app_en.arb:genderSelect] {gender, select, female {She} male {He} other {they} }
^'''), ^'''),
)), )),
); );
}); });
...@@ -2147,8 +2138,8 @@ Make sure that the specified select placeholder is defined in your arb file. ...@@ -2147,8 +2138,8 @@ Make sure that the specified select placeholder is defined in your arb file.
'message', 'message',
contains(''' contains('''
Make sure that the specified select placeholder is defined in your arb file. Make sure that the specified select placeholder is defined in your arb file.
{gender, select, female {She} male {He} other {they} } [app_en.arb:genderSelect] {gender, select, female {She} male {He} other {they} }
^'''), ^'''),
)), )),
); );
}); });
...@@ -2181,13 +2172,10 @@ Make sure that the specified select placeholder is defined in your arb file. ...@@ -2181,13 +2172,10 @@ Make sure that the specified select placeholder is defined in your arb file.
throwsA(isA<L10nException>().having( throwsA(isA<L10nException>().having(
(L10nException e) => e.message, (L10nException e) => e.message,
'message', 'message',
// TODO(thkim1011): Uncomment after work on refactoring the Message class. contains('''
// See https://github.com/flutter/flutter/issues/112709. Make sure that the specified select placeholder is defined in your arb file.
// contains(''' [app_en.arb:genderSelect] {gender, select, female {She} male {He} other {they} }
// Make sure that the specified select placeholder is defined in your arb file. ^'''),
// {gender, select, female {She} male {He} other {they} }
// ^'''),
contains('Resource attribute "@genderSelect" was not found. Please ensure that select resources have a corresponding @resource.'),
)), )),
); );
}); });
...@@ -2996,50 +2984,6 @@ AppLocalizations lookupAppLocalizations(Locale locale) { ...@@ -2996,50 +2984,6 @@ AppLocalizations lookupAppLocalizations(Locale locale) {
''')); '''));
}); });
// Regression test for https://github.com/flutter/flutter/pull/93228
testWithoutContext('should use num type for plural', () {
const String arbFile = '''
{
"tryToPollute": "{count, plural, =0{零} =1{一} other{其他}}",
"@tryToPollute": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"withoutType": "{count, plural, =0{零} =1{一} other{其他}}",
"@withoutType": {
"placeholders": {
"count": {}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(arbFile);
LocalizationsGenerator(
fileSystem: fs,
inputPathString: defaultL10nPathString,
outputPathString: defaultL10nPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
logger: logger,
)
..loadResources()
..writeOutputFiles();
final String localizationsFile = fs.file(
fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'),
).readAsStringSync();
expect(localizationsFile, containsIgnoringWhitespace(r'String tryToPollute(num count) {'));
expect(localizationsFile, containsIgnoringWhitespace(r'String withoutType(num count) {'));
});
// TODO(thkim1011): Uncomment when implementing escaping. // TODO(thkim1011): Uncomment when implementing escaping.
// See https://github.com/flutter/flutter/issues/113455. // See https://github.com/flutter/flutter/issues/113455.
// testWithoutContext('escaping with single quotes', () { // testWithoutContext('escaping with single quotes', () {
......
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