Skip to content

Commit

Permalink
ICU Message Syntax Parser (#112390)
Browse files Browse the repository at this point in the history
* init

* code generation

* improve syntax error, add tests

* add tests and fix bugs

* code generation fix

* fix all tests :)

* fix bug

* init

* fix all code gen issues

* FIXED ALL TESTS :D

* add license

* remove trailing spaces

* remove print

* tests fix

* specify type annotation

* fix test

* lint

* fix todos

* fix subclass issues

* final fix; flutter gallery runs

* escaping for later pr

* fix comment

* address PR comments

* more

* more descriptive errors

* last fixes
  • Loading branch information
thkim1011 authored Nov 5, 2022
1 parent e0e7027 commit cef4c2a
Show file tree
Hide file tree
Showing 9 changed files with 1,590 additions and 453 deletions.
542 changes: 240 additions & 302 deletions packages/flutter_tools/lib/src/localizations/gen_l10n.dart

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -135,70 +135,37 @@ const String getterTemplate = '''
String get @(name) => @(message);''';

const String methodTemplate = '''
@override
String @(name)(@(parameters)) {
return @(message);
}''';

const String formatMethodTemplate = '''
@override
String @(name)(@(parameters)) {
@(dateFormatting)
@(numberFormatting)
@(helperMethods)
return @(message);
}''';

const String pluralMethodTemplate = '''
@override
String @(name)(@(parameters)) {
@(dateFormatting)
@(numberFormatting)
return intl.Intl.pluralLogic(
@(count),
locale: localeName,
@(pluralLogicArgs),
);
}''';

const String pluralMethodTemplateInString = '''
@override
String @(name)(@(parameters)) {
@(dateFormatting)
@(numberFormatting)
final String @(variable) = intl.Intl.pluralLogic(
@(count),
locale: localeName,
@(pluralLogicArgs),
);
return @(string);
}''';

const String selectMethodTemplate = '''
@override
String @(name)(@(parameters)) {
return intl.Intl.select(
@(choice),
{
@(cases)
},
desc: '@(description)'
);
}''';

const String selectMethodTemplateInString = '''
@override
String @(name)(@(parameters)) {
final String @(variable) = intl.Intl.select(
@(choice),
{
@(cases)
},
desc: '@(description)'
);
return @(string);
}''';
const String messageHelperTemplate = '''
String @(name)(@(parameters)) {
return @(message);
}''';

const String pluralHelperTemplate = '''
String @(name)(@(parameters)) {
return intl.Intl.pluralLogic(
@(count),
locale: localeName,
@(pluralLogicArgs)
);
}''';

const String selectHelperTemplate = '''
String @(name)(@(parameters)) {
return intl.Intl.selectLogic(
@(choice),
{
@(selectCases)
},
);
}''';

const String classFileTemplate = '''
@(header)@(requiresIntlImport)import '@(fileName)';
Expand Down
83 changes: 78 additions & 5 deletions packages/flutter_tools/lib/src/localizations/gen_l10n_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,25 @@ class L10nException implements Exception {
String toString() => message;
}

class L10nParserException extends L10nException {
L10nParserException(
this.error,
this.fileName,
this.messageId,
this.messageString,
this.charNumber
): super('''
$error
[$fileName:$messageId] $messageString
${List<String>.filled(4 + fileName.length + messageId.length + charNumber, ' ').join()}^''');

final String error;
final String fileName;
final String messageId;
final String messageString;
final int charNumber;
}

// One optional named parameter to be used by a NumberFormat.
//
// Some of the NumberFormat factory constructors have optional named parameters.
Expand Down Expand Up @@ -202,16 +221,16 @@ class Placeholder {
final String resourceId;
final String name;
final String? example;
final String? type;
String? type;
final String? format;
final List<OptionalParameter> optionalParameters;
final bool? isCustomDateFormat;

bool get requiresFormatting => <String>['DateTime', 'double', 'num'].contains(type) || (type == 'int' && format != null);
bool get isNumber => <String>['double', 'int', 'num'].contains(type);
bool get requiresFormatting => requiresDateFormatting || requiresNumFormatting;
bool get requiresDateFormatting => type == 'DateTime';
bool get requiresNumFormatting => <String>['int', 'num', 'double'].contains(type) && format != null;
bool get hasValidNumberFormat => _validNumberFormats.contains(format);
bool get hasNumberFormatWithParameters => _numberFormatsWithNamedParameters.contains(format);
bool get isDate => 'DateTime' == type;
bool get hasValidDateFormat => _validDateFormats.contains(format);

static String? _stringAttribute(
Expand Down Expand Up @@ -290,6 +309,8 @@ class Placeholder {
// 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.
// 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 {
Message(Map<String, Object?> bundle, this.resourceId, bool isResourceAttributeRequired)
: assert(bundle != null),
Expand All @@ -298,7 +319,12 @@ class Message {
description = _description(bundle, resourceId, isResourceAttributeRequired),
placeholders = _placeholders(bundle, resourceId, isResourceAttributeRequired),
_pluralMatch = _pluralRE.firstMatch(_value(bundle, resourceId)),
_selectMatch = _selectRE.firstMatch(_value(bundle, resourceId));
_selectMatch = _selectRE.firstMatch(_value(bundle, resourceId)) {
if (isPlural) {
final Placeholder placeholder = getCountPlaceholder();
placeholder.type = 'num';
}
}

static final RegExp _pluralRE = RegExp(r'\s*\{([\w\s,]*),\s*plural\s*,');
static final RegExp _selectRE = RegExp(r'\s*\{([\w\s,]*),\s*select\s*,');
Expand Down Expand Up @@ -769,3 +795,50 @@ final Set<String> _iso639Languages = <String>{
'zh',
'zu',
};

// Used in LocalizationsGenerator._generateMethod.generateHelperMethod.
class HelperMethod {
HelperMethod(this.dependentPlaceholders, {this.helper, this.placeholder, this.string }):
assert((() {
// At least one of helper, placeholder, string must be nonnull.
final bool a = helper == null;
final bool b = placeholder == null;
final bool c = string == null;
return (!a && b && c) || (a && !b && c) || (a && b && !c);
})());

Set<Placeholder> dependentPlaceholders;
String? helper;
Placeholder? placeholder;
String? string;

String get helperOrPlaceholder {
if (helper != null) {
return '$helper($methodArguments)';
} else if (string != null) {
return '$string';
} else {
if (placeholder!.requiresFormatting) {
return '${placeholder!.name}String';
} else {
return placeholder!.name;
}
}
}

String get methodParameters {
assert(helper != null);
return dependentPlaceholders.map((Placeholder placeholder) =>
(placeholder.requiresFormatting)
? 'String ${placeholder.name}String'
: '${placeholder.type} ${placeholder.name}').join(', ');
}

String get methodArguments {
assert(helper != null);
return dependentPlaceholders.map((Placeholder placeholder) =>
(placeholder.requiresFormatting)
? '${placeholder.name}String'
: placeholder.name).join(', ');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,34 @@ String generateString(String value) {
// Reintroduce escaped backslashes into generated Dart string.
.replaceAll(backslash, r'\\');

return "'$value'";
return value;
}

/// Given a list of strings, placeholders, or helper function calls, concatenate
/// them into one expression to be returned.
String generateReturnExpr(List<HelperMethod> helpers) {
if (helpers.isEmpty) {
return "''";
} else if (
helpers.length == 1
&& helpers[0].string == null
&& (helpers[0].placeholder?.type == 'String' || helpers[0].helper != null)
) {
return helpers[0].helperOrPlaceholder;
} else {
final String string = helpers.reversed.fold<String>('', (String string, HelperMethod helper) {
if (helper.string != null) {
return generateString(helper.string!) + string;
}
final RegExp alphanumeric = RegExp(r'^([0-9a-zA-Z]|_)+$');
if (alphanumeric.hasMatch(helper.helperOrPlaceholder) && !(string.isNotEmpty && alphanumeric.hasMatch(string[0]))) {
return '\$${helper.helperOrPlaceholder}$string';
} else {
return '\${${helper.helperOrPlaceholder}}$string';
}
});
return "'$string'";
}
}

/// Typed configuration from the localizations config file.
Expand Down
Loading

0 comments on commit cef4c2a

Please sign in to comment.