[gen_l10n] When localizing a message, prefer placeholder definitions defined by the current locale rather than the template locale (#153459)
- Fixes #153457 - Fixes #116716
This commit is contained in:
parent
f35cd558f8
commit
dca37ad17f
@ -123,23 +123,36 @@ String _syntheticL10nPackagePath(FileSystem fileSystem) => fileSystem.path.join(
|
||||
// For example, if placeholders are used for plurals and no type was specified, then the type will
|
||||
// automatically set to 'num'. Similarly, if such placeholders are used for selects, then the type
|
||||
// will be set to 'String'. For such placeholders that are used for both, we should throw an error.
|
||||
List<String> generateMethodParameters(Message message, bool useNamedParameters) {
|
||||
return message.placeholders.values.map((Placeholder placeholder) {
|
||||
List<String> generateMethodParameters(Message message, LocaleInfo? locale, bool useNamedParameters) {
|
||||
|
||||
// Check the compatibility of template placeholders and locale placeholders.
|
||||
final Map<String, Placeholder>? localePlaceholders = message.localePlaceholders[locale];
|
||||
|
||||
return message.templatePlaceholders.entries.map((MapEntry<String, Placeholder> e) {
|
||||
final Placeholder placeholder = e.value;
|
||||
final Placeholder? localePlaceholder = localePlaceholders?[e.key];
|
||||
if (localePlaceholder != null && placeholder.type != localePlaceholder.type) {
|
||||
throw L10nException(
|
||||
'The placeholder, ${placeholder.name}, has its "type" resource attribute set to '
|
||||
'the "${localePlaceholder.type}" type in locale "$locale", but it is "${placeholder.type}" '
|
||||
'in the template placeholder. For compatibility with template placeholder, change '
|
||||
'the "type" attribute to "${placeholder.type}".');
|
||||
}
|
||||
return '${useNamedParameters ? 'required ' : ''}${placeholder.type} ${placeholder.name}';
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// Similar to above, but is used for passing arguments into helper functions.
|
||||
List<String> generateMethodArguments(Message message) {
|
||||
return message.placeholders.values.map((Placeholder placeholder) => placeholder.name).toList();
|
||||
return message.templatePlaceholders.values.map((Placeholder placeholder) => placeholder.name).toList();
|
||||
}
|
||||
|
||||
String generateDateFormattingLogic(Message message) {
|
||||
if (message.placeholders.isEmpty || !message.placeholdersRequireFormatting) {
|
||||
String generateDateFormattingLogic(Message message, LocaleInfo locale) {
|
||||
if (message.templatePlaceholders.isEmpty) {
|
||||
return '@(none)';
|
||||
}
|
||||
|
||||
final Iterable<String> formatStatements = message.placeholders.values
|
||||
final Iterable<String> formatStatements = message.getPlaceholders(locale)
|
||||
.where((Placeholder placeholder) => placeholder.requiresDateFormatting)
|
||||
.map((Placeholder placeholder) {
|
||||
final String? placeholderFormat = placeholder.format;
|
||||
@ -177,12 +190,12 @@ String generateDateFormattingLogic(Message message) {
|
||||
return formatStatements.isEmpty ? '@(none)' : formatStatements.join();
|
||||
}
|
||||
|
||||
String generateNumberFormattingLogic(Message message) {
|
||||
if (message.placeholders.isEmpty || !message.placeholdersRequireFormatting) {
|
||||
String generateNumberFormattingLogic(Message message, LocaleInfo locale) {
|
||||
if (message.templatePlaceholders.isEmpty) {
|
||||
return '@(none)';
|
||||
}
|
||||
|
||||
final Iterable<String> formatStatements = message.placeholders.values
|
||||
final Iterable<String> formatStatements = message.getPlaceholders(locale)
|
||||
.where((Placeholder placeholder) => placeholder.requiresNumFormatting)
|
||||
.map((Placeholder placeholder) {
|
||||
final String? placeholderFormat = placeholder.format;
|
||||
@ -242,12 +255,12 @@ String generateBaseClassMethod(Message message, LocaleInfo? templateArbLocale, b
|
||||
/// In $templateArbLocale, this message translates to:
|
||||
/// **'${generateString(message.value)}'**''';
|
||||
|
||||
if (message.placeholders.isNotEmpty) {
|
||||
if (message.templatePlaceholders.isNotEmpty) {
|
||||
return (useNamedParameters ? baseClassMethodWithNamedParameterTemplate : baseClassMethodTemplate)
|
||||
.replaceAll('@(comment)', comment)
|
||||
.replaceAll('@(templateLocaleTranslationComment)', templateLocaleTranslationComment)
|
||||
.replaceAll('@(name)', message.resourceId)
|
||||
.replaceAll('@(parameters)', generateMethodParameters(message, useNamedParameters).join(', '));
|
||||
.replaceAll('@(parameters)', generateMethodParameters(message, null, useNamedParameters).join(', '));
|
||||
}
|
||||
return baseClassGetterTemplate
|
||||
.replaceAll('@(comment)', comment)
|
||||
@ -610,10 +623,6 @@ class LocalizationsGenerator {
|
||||
/// ['es', 'en'] is passed in, the 'es' locale will take priority over 'en'.
|
||||
final List<LocaleInfo> preferredSupportedLocales;
|
||||
|
||||
// Whether we need to import intl or not. This flag is updated after parsing
|
||||
// all of the messages.
|
||||
bool requiresIntlImport = false;
|
||||
|
||||
// Whether we want to use escaping for ICU messages.
|
||||
bool useEscaping = false;
|
||||
|
||||
@ -993,8 +1002,7 @@ class LocalizationsGenerator {
|
||||
.replaceAll('@(fileName)', fileName)
|
||||
.replaceAll('@(class)', '$className${locale.camelCase()}')
|
||||
.replaceAll('@(localeName)', locale.toString())
|
||||
.replaceAll('@(methods)', methods.join('\n\n'))
|
||||
.replaceAll('@(requiresIntlImport)', requiresIntlImport ? "import 'package:intl/intl.dart' as intl;\n\n" : '');
|
||||
.replaceAll('@(methods)', methods.join('\n\n'));
|
||||
}
|
||||
|
||||
String _generateSubclass(
|
||||
@ -1143,7 +1151,6 @@ class LocalizationsGenerator {
|
||||
.replaceAll('@(messageClassImports)', sortedClassImports.join('\n'))
|
||||
.replaceAll('@(delegateClass)', delegateClass)
|
||||
.replaceAll('@(requiresFoundationImport)', useDeferredLoading ? '' : "import 'package:flutter/foundation.dart';")
|
||||
.replaceAll('@(requiresIntlImport)', requiresIntlImport ? "import 'package:intl/intl.dart' as intl;" : '')
|
||||
.replaceAll('@(canBeNullable)', usesNullableGetter ? '?' : '')
|
||||
.replaceAll('@(needsNullCheck)', usesNullableGetter ? '' : '!')
|
||||
// Removes all trailing whitespace from the generated file.
|
||||
@ -1154,15 +1161,10 @@ class LocalizationsGenerator {
|
||||
|
||||
String _generateMethod(Message message, LocaleInfo locale) {
|
||||
try {
|
||||
// Determine if we must import intl for date or number formatting.
|
||||
if (message.placeholdersRequireFormatting) {
|
||||
requiresIntlImport = true;
|
||||
}
|
||||
|
||||
final String translationForMessage = message.messages[locale]!;
|
||||
final Node node = message.parsedMessages[locale]!;
|
||||
// If the placeholders list is empty, then return a getter method.
|
||||
if (message.placeholders.isEmpty) {
|
||||
if (message.templatePlaceholders.isEmpty) {
|
||||
// Use the parsed translation to handle escaping with the same behavior.
|
||||
return getterTemplate
|
||||
.replaceAll('@(name)', message.resourceId)
|
||||
@ -1196,14 +1198,13 @@ class LocalizationsGenerator {
|
||||
case ST.placeholderExpr:
|
||||
assert(node.children[1].type == ST.identifier);
|
||||
final String identifier = node.children[1].value!;
|
||||
final Placeholder placeholder = message.placeholders[identifier]!;
|
||||
final Placeholder placeholder = message.localePlaceholders[locale]?[identifier] ?? message.templatePlaceholders[identifier]!;
|
||||
if (placeholder.requiresFormatting) {
|
||||
return '\$${node.children[1].value}String';
|
||||
}
|
||||
return '\$${node.children[1].value}';
|
||||
|
||||
case ST.pluralExpr:
|
||||
requiresIntlImport = true;
|
||||
final Map<String, String> pluralLogicArgs = <String, String>{};
|
||||
// Recall that pluralExpr are of the form
|
||||
// pluralExpr := "{" ID "," "plural" "," pluralParts "}"
|
||||
@ -1259,7 +1260,6 @@ The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", "
|
||||
return '\$$tempVarName';
|
||||
|
||||
case ST.selectExpr:
|
||||
requiresIntlImport = true;
|
||||
// Recall that pluralExpr are of the form
|
||||
// pluralExpr := "{" ID "," "plural" "," pluralParts "}"
|
||||
assert(node.children[1].type == ST.identifier);
|
||||
@ -1284,7 +1284,6 @@ The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", "
|
||||
);
|
||||
return '\$$tempVarName';
|
||||
case ST.argumentExpr:
|
||||
requiresIntlImport = true;
|
||||
assert(node.children[1].type == ST.identifier);
|
||||
assert(node.children[3].type == ST.argType);
|
||||
assert(node.children[7].type == ST.identifier);
|
||||
@ -1320,9 +1319,9 @@ The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", "
|
||||
final String tempVarLines = tempVariables.isEmpty ? '' : '${tempVariables.join('\n')}\n';
|
||||
return (useNamedParameters ? methodWithNamedParameterTemplate : methodTemplate)
|
||||
.replaceAll('@(name)', message.resourceId)
|
||||
.replaceAll('@(parameters)', generateMethodParameters(message, useNamedParameters).join(', '))
|
||||
.replaceAll('@(dateFormatting)', generateDateFormattingLogic(message))
|
||||
.replaceAll('@(numberFormatting)', generateNumberFormattingLogic(message))
|
||||
.replaceAll('@(parameters)', generateMethodParameters(message, locale, useNamedParameters).join(', '))
|
||||
.replaceAll('@(dateFormatting)', generateDateFormattingLogic(message, locale))
|
||||
.replaceAll('@(numberFormatting)', generateNumberFormattingLogic(message, locale))
|
||||
.replaceAll('@(tempVars)', tempVarLines)
|
||||
.replaceAll('@(message)', messageString)
|
||||
.replaceAll('@(none)\n', '');
|
||||
|
@ -171,7 +171,9 @@ const String dateVariableTemplate = '''
|
||||
String @(varName) = intl.DateFormat.@(formatType)(localeName).format(@(argument));''';
|
||||
|
||||
const String classFileTemplate = '''
|
||||
@(header)@(requiresIntlImport)import '@(fileName)';
|
||||
@(header)// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import '@(fileName)';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
|
@ -347,7 +347,8 @@ class Message {
|
||||
) : assert(resourceId.isNotEmpty),
|
||||
value = _value(templateBundle.resources, resourceId),
|
||||
description = _description(templateBundle.resources, resourceId, isResourceAttributeRequired),
|
||||
placeholders = _placeholders(templateBundle.resources, resourceId, isResourceAttributeRequired),
|
||||
templatePlaceholders = _placeholders(templateBundle.resources, resourceId, isResourceAttributeRequired),
|
||||
localePlaceholders = <LocaleInfo, Map<String, Placeholder>>{},
|
||||
messages = <LocaleInfo, String?>{},
|
||||
parsedMessages = <LocaleInfo, Node?>{} {
|
||||
// Filenames for error handling.
|
||||
@ -357,9 +358,14 @@ class Message {
|
||||
filenames[bundle.locale] = bundle.file.basename;
|
||||
final String? translation = bundle.translationFor(resourceId);
|
||||
messages[bundle.locale] = translation;
|
||||
|
||||
localePlaceholders[bundle.locale] = templateBundle.locale == bundle.locale
|
||||
? templatePlaceholders
|
||||
: _placeholders(bundle.resources, resourceId, false);
|
||||
|
||||
List<String>? validPlaceholders;
|
||||
if (useRelaxedSyntax) {
|
||||
validPlaceholders = placeholders.entries.map((MapEntry<String, Placeholder> e) => e.key).toList();
|
||||
validPlaceholders = templatePlaceholders.entries.map((MapEntry<String, Placeholder> e) => e.key).toList();
|
||||
}
|
||||
try {
|
||||
parsedMessages[bundle.locale] = translation == null ? null : Parser(
|
||||
@ -378,7 +384,7 @@ class Message {
|
||||
}
|
||||
}
|
||||
// Infer the placeholders
|
||||
_inferPlaceholders(filenames);
|
||||
_inferPlaceholders();
|
||||
}
|
||||
|
||||
final String resourceId;
|
||||
@ -386,13 +392,21 @@ class Message {
|
||||
final String? description;
|
||||
late final Map<LocaleInfo, String?> messages;
|
||||
final Map<LocaleInfo, Node?> parsedMessages;
|
||||
final Map<String, Placeholder> placeholders;
|
||||
final Map<LocaleInfo, Map<String, Placeholder>> localePlaceholders;
|
||||
final Map<String, Placeholder> templatePlaceholders;
|
||||
final bool useEscaping;
|
||||
final bool useRelaxedSyntax;
|
||||
final Logger? logger;
|
||||
bool hadErrors = false;
|
||||
|
||||
bool get placeholdersRequireFormatting => placeholders.values.any((Placeholder p) => p.requiresFormatting);
|
||||
Iterable<Placeholder> getPlaceholders(LocaleInfo locale) {
|
||||
final Map<String, Placeholder>? placeholders = localePlaceholders[locale];
|
||||
if (placeholders == null) {
|
||||
return templatePlaceholders.values;
|
||||
}
|
||||
return templatePlaceholders.values
|
||||
.map((Placeholder templatePlaceholder) => placeholders[templatePlaceholder.name] ?? templatePlaceholder);
|
||||
}
|
||||
|
||||
static String _value(Map<String, Object?> bundle, String resourceId) {
|
||||
final Object? value = bundle[resourceId];
|
||||
@ -488,12 +502,15 @@ class Message {
|
||||
|
||||
// Using parsed translations, attempt to infer types of placeholders used by plurals and selects.
|
||||
// For undeclared placeholders, create a new placeholder.
|
||||
void _inferPlaceholders(Map<LocaleInfo, String> filenames) {
|
||||
void _inferPlaceholders() {
|
||||
// We keep the undeclared placeholders separate so that we can sort them alphabetically afterwards.
|
||||
final Map<String, Placeholder> undeclaredPlaceholders = <String, Placeholder>{};
|
||||
// Helper for getting placeholder by name.
|
||||
Placeholder? getPlaceholder(String name) => placeholders[name] ?? undeclaredPlaceholders[name];
|
||||
for (final LocaleInfo locale in parsedMessages.keys) {
|
||||
Placeholder? getPlaceholder(String name) =>
|
||||
localePlaceholders[locale]?[name] ??
|
||||
templatePlaceholders[name] ??
|
||||
undeclaredPlaceholders[name];
|
||||
if (parsedMessages[locale] == null) {
|
||||
continue;
|
||||
}
|
||||
@ -529,7 +546,7 @@ class Message {
|
||||
traversalStack.addAll(node.children);
|
||||
}
|
||||
}
|
||||
placeholders.addEntries(
|
||||
templatePlaceholders.addEntries(
|
||||
undeclaredPlaceholders.entries
|
||||
.toList()
|
||||
..sort((MapEntry<String, Placeholder> p1, MapEntry<String, Placeholder> p2) => p1.key.compareTo(p2.key))
|
||||
@ -542,7 +559,7 @@ class Message {
|
||||
|| !x && !y && !z;
|
||||
}
|
||||
|
||||
for (final Placeholder placeholder in placeholders.values) {
|
||||
for (final Placeholder placeholder in templatePlaceholders.values) {
|
||||
if (!atMostOneOf(placeholder.isPlural, placeholder.isDateTime, placeholder.isSelect)) {
|
||||
throw L10nException('Placeholder is used as plural/select/datetime in certain languages.');
|
||||
} else if (placeholder.isPlural) {
|
||||
|
@ -792,6 +792,8 @@ flutter:
|
||||
expect(fs.file('/lib/l10n/bar_en.dart').readAsStringSync(), '''
|
||||
HEADER
|
||||
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'bar.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
@ -894,6 +896,8 @@ flutter:\r
|
||||
);
|
||||
|
||||
expect(fs.file('/lib/l10n/app_localizations_en.dart').readAsStringSync(), '''
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
@ -927,6 +931,8 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
expect(fs.file('/lib/l10n/app_localizations_en.dart').readAsStringSync(), '''
|
||||
HEADER
|
||||
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
@ -1705,6 +1711,270 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
|
||||
)),
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('handle date with multiple locale', () {
|
||||
setupLocalizations(<String, String>{
|
||||
'en': '''
|
||||
{
|
||||
"@@locale": "en",
|
||||
"springBegins": "Spring begins on {springStartDate}",
|
||||
"@springBegins": {
|
||||
"description": "The first day of spring",
|
||||
"placeholders": {
|
||||
"springStartDate": {
|
||||
"type": "DateTime",
|
||||
"format": "MMMd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}''',
|
||||
'ja': '''
|
||||
{
|
||||
"@@locale": "ja",
|
||||
"springBegins": "春が始まるのは{springStartDate}",
|
||||
"@springBegins": {
|
||||
"placeholders": {
|
||||
"springStartDate": {
|
||||
"type": "DateTime",
|
||||
"format": "MMMMd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}'''
|
||||
});
|
||||
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('intl.DateFormat.MMMd(localeName)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('intl.DateFormat.MMMMd(localeName)'));
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('String springBegins(DateTime springStartDate)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('String springBegins(DateTime springStartDate)'));
|
||||
});
|
||||
|
||||
testWithoutContext('handle date with multiple locale when only template has placeholders', () {
|
||||
setupLocalizations(<String, String>{
|
||||
'en': '''
|
||||
{
|
||||
"@@locale": "en",
|
||||
"springBegins": "Spring begins on {springStartDate}",
|
||||
"@springBegins": {
|
||||
"description": "The first day of spring",
|
||||
"placeholders": {
|
||||
"springStartDate": {
|
||||
"type": "DateTime",
|
||||
"format": "MMMd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}''',
|
||||
'ja': '''
|
||||
{
|
||||
"@@locale": "ja",
|
||||
"springBegins": "春が始まるのは{springStartDate}"
|
||||
}'''
|
||||
});
|
||||
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('intl.DateFormat.MMMd(localeName)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('intl.DateFormat.MMMd(localeName)'));
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('String springBegins(DateTime springStartDate)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('String springBegins(DateTime springStartDate)'));
|
||||
});
|
||||
|
||||
testWithoutContext('handle date with multiple locale when there is unused placeholder', () {
|
||||
setupLocalizations(<String, String>{
|
||||
'en': '''
|
||||
{
|
||||
"@@locale": "en",
|
||||
"springBegins": "Spring begins on {springStartDate}",
|
||||
"@springBegins": {
|
||||
"description": "The first day of spring",
|
||||
"placeholders": {
|
||||
"springStartDate": {
|
||||
"type": "DateTime",
|
||||
"format": "MMMd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}''',
|
||||
'ja': '''
|
||||
{
|
||||
"@@locale": "ja",
|
||||
"springBegins": "春が始まるのは{springStartDate}",
|
||||
"@springBegins": {
|
||||
"description": "The first day of spring",
|
||||
"placeholders": {
|
||||
"notUsed": {
|
||||
"type": "DateTime",
|
||||
"format": "MMMMd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}'''
|
||||
});
|
||||
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('intl.DateFormat.MMMd(localeName)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('intl.DateFormat.MMMd(localeName)'));
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('String springBegins(DateTime springStartDate)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('String springBegins(DateTime springStartDate)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), isNot(contains('notUsed')));
|
||||
});
|
||||
|
||||
testWithoutContext('handle date with multiple locale when placeholders are incompatible', () {
|
||||
expect(
|
||||
() {
|
||||
setupLocalizations(<String, String>{
|
||||
'en': '''
|
||||
{
|
||||
"@@locale": "en",
|
||||
"springBegins": "Spring begins on {springStartDate}",
|
||||
"@springBegins": {
|
||||
"description": "The first day of spring",
|
||||
"placeholders": {
|
||||
"springStartDate": {
|
||||
"type": "DateTime",
|
||||
"format": "MMMd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}''',
|
||||
'ja': '''
|
||||
{
|
||||
"@@locale": "ja",
|
||||
"springBegins": "春が始まるのは{springStartDate}",
|
||||
"@springBegins": {
|
||||
"description": "The first day of spring",
|
||||
"placeholders": {
|
||||
"springStartDate": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
}
|
||||
}'''
|
||||
});
|
||||
},
|
||||
throwsA(isA<L10nException>().having(
|
||||
(L10nException e) => e.message,
|
||||
'message',
|
||||
contains('The placeholder, springStartDate, has its "type" resource attribute set to the "String" type in locale "ja", but it is "DateTime" in the template placeholder.'),
|
||||
)),
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('handle date with multiple locale when non-template placeholder does not specify type', () {
|
||||
expect(
|
||||
() {
|
||||
setupLocalizations(<String, String>{
|
||||
'en': '''
|
||||
{
|
||||
"@@locale": "en",
|
||||
"springBegins": "Spring begins on {springStartDate}",
|
||||
"@springBegins": {
|
||||
"description": "The first day of spring",
|
||||
"placeholders": {
|
||||
"springStartDate": {
|
||||
"type": "DateTime",
|
||||
"format": "MMMd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}''',
|
||||
'ja': '''
|
||||
{
|
||||
"@@locale": "ja",
|
||||
"springBegins": "春が始まるのは{springStartDate}",
|
||||
"@springBegins": {
|
||||
"description": "The first day of spring",
|
||||
"placeholders": {
|
||||
"springStartDate": {
|
||||
"format": "MMMMd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}'''
|
||||
});
|
||||
},
|
||||
throwsA(isA<L10nException>().having(
|
||||
(L10nException e) => e.message,
|
||||
'message',
|
||||
contains('The placeholder, springStartDate, has its "type" resource attribute set to the "null" type in locale "ja", but it is "DateTime" in the template placeholder.'),
|
||||
)),
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('handle ordinary formatted date and arbitrary formatted date', () {
|
||||
setupLocalizations(<String, String>{
|
||||
'en': '''
|
||||
{
|
||||
"@@locale": "en",
|
||||
"springBegins": "Spring begins on {springStartDate}",
|
||||
"@springBegins": {
|
||||
"description": "The first day of spring",
|
||||
"placeholders": {
|
||||
"springStartDate": {
|
||||
"type": "DateTime",
|
||||
"format": "MMMd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}''',
|
||||
'ja': '''
|
||||
{
|
||||
"@@locale": "ja",
|
||||
"springBegins": "春が始まるのは{springStartDate}",
|
||||
"@springBegins": {
|
||||
"placeholders": {
|
||||
"springStartDate": {
|
||||
"type": "DateTime",
|
||||
"format": "立春",
|
||||
"isCustomDateFormat": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}'''
|
||||
});
|
||||
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('intl.DateFormat.MMMd(localeName)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains(r"DateFormat('立春', localeName)"));
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('String springBegins(DateTime springStartDate)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('String springBegins(DateTime springStartDate)'));
|
||||
});
|
||||
|
||||
testWithoutContext('handle arbitrary formatted date with multiple locale', () {
|
||||
setupLocalizations(<String, String>{
|
||||
'en': '''
|
||||
{
|
||||
"@@locale": "en",
|
||||
"springBegins": "Spring begins on {springStartDate}",
|
||||
"@springBegins": {
|
||||
"description": "The first day of spring",
|
||||
"placeholders": {
|
||||
"springStartDate": {
|
||||
"type": "DateTime",
|
||||
"format": "asdf o'clock",
|
||||
"isCustomDateFormat": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}''',
|
||||
'ja': '''
|
||||
{
|
||||
"@@locale": "ja",
|
||||
"springBegins": "春が始まるのは{springStartDate}",
|
||||
"@springBegins": {
|
||||
"placeholders": {
|
||||
"springStartDate": {
|
||||
"type": "DateTime",
|
||||
"format": "立春",
|
||||
"isCustomDateFormat": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}'''
|
||||
});
|
||||
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains(r"DateFormat('asdf o\'clock', localeName)"));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains(r"DateFormat('立春', localeName)"));
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('String springBegins(DateTime springStartDate)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('String springBegins(DateTime springStartDate)'));
|
||||
});
|
||||
});
|
||||
|
||||
group('NumberFormat tests', () {
|
||||
@ -1766,7 +2036,7 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
|
||||
'en': singleMessageArbFileString,
|
||||
'es': singleEsMessageArbFileString,
|
||||
});
|
||||
expect(getGeneratedFileContent(locale: 'es'), isNot(contains(intlImportDartCode)));
|
||||
expect(getGeneratedFileContent(locale: 'es'), contains(intlImportDartCode));
|
||||
});
|
||||
|
||||
testWithoutContext('warnings are generated when plural parts are repeated', () {
|
||||
@ -2425,6 +2695,128 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
|
||||
)),
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('handle number with multiple locale', () {
|
||||
setupLocalizations(<String, String>{
|
||||
'en': '''
|
||||
{
|
||||
"@@locale": "en",
|
||||
"money": "Sum {number}",
|
||||
"@money": {
|
||||
"placeholders": {
|
||||
"number": {
|
||||
"type": "int",
|
||||
"format": "currency"
|
||||
}
|
||||
}
|
||||
}
|
||||
}''',
|
||||
'ja': '''
|
||||
{
|
||||
"@@locale": "ja",
|
||||
"money": "合計 {number}",
|
||||
"@money": {
|
||||
"placeholders": {
|
||||
"number": {
|
||||
"type": "int",
|
||||
"format": "decimalPatternDigits",
|
||||
"optionalParameters": {
|
||||
"decimalDigits": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}'''
|
||||
});
|
||||
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('String money(int number)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('String money(int number)'));
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('intl.NumberFormat.currency('));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('intl.NumberFormat.decimalPatternDigits('));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('decimalDigits: 3'));
|
||||
});
|
||||
|
||||
testWithoutContext('handle number with multiple locale specifying a format only in template', () {
|
||||
setupLocalizations(<String, String>{
|
||||
'en': '''
|
||||
{
|
||||
"@@locale": "en",
|
||||
"money": "Sum {number}",
|
||||
"@money": {
|
||||
"placeholders": {
|
||||
"number": {
|
||||
"type": "int",
|
||||
"format": "decimalPatternDigits",
|
||||
"optionalParameters": {
|
||||
"decimalDigits": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}''',
|
||||
'ja': '''
|
||||
{
|
||||
"@@locale": "ja",
|
||||
"money": "合計 {number}",
|
||||
"@money": {
|
||||
"placeholders": {
|
||||
"number": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
}'''
|
||||
});
|
||||
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('String money(int number)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('String money(int number)'));
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('intl.NumberFormat.decimalPatternDigits('));
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('decimalDigits: 3'));
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains(r"return 'Sum $numberString'"));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), isNot(contains('intl.NumberFormat')));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains(r"return '合計 $number'"));
|
||||
});
|
||||
|
||||
testWithoutContext('handle number with multiple locale specifying a format only in non-template', () {
|
||||
setupLocalizations(<String, String>{
|
||||
'en': '''
|
||||
{
|
||||
"@@locale": "en",
|
||||
"money": "Sum {number}",
|
||||
"@money": {
|
||||
"placeholders": {
|
||||
"number": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
}''',
|
||||
'ja': '''
|
||||
{
|
||||
"@@locale": "ja",
|
||||
"money": "合計 {number}",
|
||||
"@money": {
|
||||
"placeholders": {
|
||||
"number": {
|
||||
"type": "int",
|
||||
"format": "decimalPatternDigits",
|
||||
"optionalParameters": {
|
||||
"decimalDigits": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}'''
|
||||
});
|
||||
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains('String money(int number)'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('String money(int number)'));
|
||||
expect(getGeneratedFileContent(locale: 'en'), isNot(contains('intl.NumberFormat')));
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains(r"return 'Sum $number'"));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('intl.NumberFormat.decimalPatternDigits('));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains('decimalDigits: 3'));
|
||||
expect(getGeneratedFileContent(locale: 'ja'), contains(r"return '合計 $numberString'"));
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('should generate a valid pubspec.yaml file when using synthetic package if it does not already exist', () {
|
||||
@ -2477,7 +2869,7 @@ String orderNumber(int number) {
|
||||
return 'This is order #$number.';
|
||||
}
|
||||
'''));
|
||||
expect(getGeneratedFileContent(locale: 'en'), isNot(contains(intlImportDartCode)));
|
||||
expect(getGeneratedFileContent(locale: 'en'), contains(intlImportDartCode));
|
||||
});
|
||||
|
||||
testWithoutContext('app localizations lookup is a public method', () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user