master #81
@ -43,14 +43,30 @@ class App extends StatelessWidget {
|
|||||||
// test
|
// test
|
||||||
const I18nDataSource dataSource = AssetsFileDataSourceImpl();
|
const I18nDataSource dataSource = AssetsFileDataSourceImpl();
|
||||||
|
|
||||||
const I18nRepository repository =
|
final I18nRepository repository =
|
||||||
I18RepositoryImpl(dataSource: dataSource);
|
I18nRepositoryImpl(dataSource: dataSource);
|
||||||
|
|
||||||
final test = (await repository.load('en')).ok;
|
final test1 = (await repository.load(locale: 'en')).ok;
|
||||||
|
// final test2 = (await repository.loadFrom(
|
||||||
|
// Uri.parse('assets/i18n.en.yaml'),
|
||||||
|
// parser: const YamlParser(),
|
||||||
|
// ))
|
||||||
|
// .ok;
|
||||||
|
|
||||||
print(test?.locale);
|
// print(test1?.locale);
|
||||||
print(test?.data);
|
// print(test1?.data);
|
||||||
print(test?.data['btnAddFile']);
|
// print(test1?.data['btnAddFile']);
|
||||||
|
|
||||||
|
final text = repository.get('youHavePushed', {
|
||||||
|
'count': 8,
|
||||||
|
});
|
||||||
|
|
||||||
|
print(text.ok);
|
||||||
|
|
||||||
|
|
||||||
|
// print(test2?.locale);
|
||||||
|
// print(test2?.data);
|
||||||
|
// print(test2?.data['btnAddFile']);
|
||||||
|
|
||||||
return 'test';
|
return 'test';
|
||||||
}),
|
}),
|
||||||
|
135
packages/wyatt_i18n/lib/src/core/utils/i18n_parser.dart
Normal file
135
packages/wyatt_i18n/lib/src/core/utils/i18n_parser.dart
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// Copyright (C) 2023 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:wyatt_i18n/src/core/utils/icu_parser.dart';
|
||||||
|
import 'package:wyatt_i18n/src/domain/entities/tokens.dart';
|
||||||
|
import 'package:wyatt_i18n/wyatt_i18n.dart';
|
||||||
|
|
||||||
|
class I18nParser extends Parser<String, String> {
|
||||||
|
const I18nParser({
|
||||||
|
required this.i18n,
|
||||||
|
this.arguments = const {},
|
||||||
|
}) : super();
|
||||||
|
|
||||||
|
final I18n i18n;
|
||||||
|
final Map<String, dynamic> arguments;
|
||||||
|
|
||||||
|
dynamic getArgument(String key) {
|
||||||
|
final arg = arguments[key];
|
||||||
|
if (arg == null) {
|
||||||
|
throw ArgumentsRequiredException(key, 'value');
|
||||||
|
}
|
||||||
|
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
String pluralToString(Plural token) {
|
||||||
|
final List<Option?> options = token.options;
|
||||||
|
final zero =
|
||||||
|
options.firstWhereOrNull((o) => o!.name == 'zero' || o.name == '=0');
|
||||||
|
final one =
|
||||||
|
options.firstWhereOrNull((o) => o!.name == 'one' || o.name == '=1');
|
||||||
|
final two =
|
||||||
|
options.firstWhereOrNull((o) => o!.name == 'two' || o.name == '=2');
|
||||||
|
final few = options.firstWhereOrNull((o) => o!.name == 'few');
|
||||||
|
final many = options.firstWhereOrNull((o) => o!.name == 'many');
|
||||||
|
final other = options.firstWhereOrNull((o) => o!.name == 'other');
|
||||||
|
final zeroStr = zero?.value.map(parsedElementToString).join() ?? '';
|
||||||
|
final oneStr = one?.value.map(parsedElementToString).join() ?? '';
|
||||||
|
final twoStr = two?.value.map(parsedElementToString).join() ?? '';
|
||||||
|
final fewStr = few?.value.map(parsedElementToString).join() ?? '';
|
||||||
|
final manyStr = many?.value.map(parsedElementToString).join() ?? '';
|
||||||
|
final otherStr = other?.value.map(parsedElementToString).join() ?? '';
|
||||||
|
|
||||||
|
return Intl.plural(
|
||||||
|
getArgument(token.value) as num,
|
||||||
|
zero: zeroStr,
|
||||||
|
two: twoStr,
|
||||||
|
one: oneStr,
|
||||||
|
few: fewStr,
|
||||||
|
other: otherStr,
|
||||||
|
many: manyStr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String genderToString(Gender token) {
|
||||||
|
final List<Option?> options = token.options;
|
||||||
|
final other = options.firstWhereOrNull((o) => o!.name == 'other');
|
||||||
|
final male = options.firstWhereOrNull((o) => o!.name == 'male');
|
||||||
|
final female = options.firstWhereOrNull((o) => o!.name == 'female');
|
||||||
|
final otherStr = other?.value.map(parsedElementToString).join() ?? '';
|
||||||
|
final maleStr = male?.value.map(parsedElementToString).join() ?? '';
|
||||||
|
final femaleStr = female?.value.map(parsedElementToString).join() ?? '';
|
||||||
|
|
||||||
|
return Intl.gender(
|
||||||
|
getArgument(token.value) as String,
|
||||||
|
other: otherStr,
|
||||||
|
female: femaleStr,
|
||||||
|
male: maleStr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String selectToString(Select token) {
|
||||||
|
final Map<Object, String> cases = {
|
||||||
|
for (var e in token.options.map(
|
||||||
|
(o) => MapEntry(
|
||||||
|
o.name,
|
||||||
|
o.value.map(parsedElementToString).join(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
e.key: e.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Intl.select(
|
||||||
|
getArgument(token.value) as String,
|
||||||
|
cases,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String parsedElementToString(Token token) {
|
||||||
|
switch (token.type) {
|
||||||
|
case TokenType.literal:
|
||||||
|
return token.value;
|
||||||
|
case TokenType.plural:
|
||||||
|
return pluralToString(token as Plural);
|
||||||
|
case TokenType.gender:
|
||||||
|
return genderToString(token as Gender);
|
||||||
|
case TokenType.argument:
|
||||||
|
return getArgument(token.value).toString();
|
||||||
|
case TokenType.select:
|
||||||
|
return selectToString(token as Select);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String parse(String input, {String? key}) {
|
||||||
|
String? result;
|
||||||
|
try {
|
||||||
|
result = IcuParser().parse(input)?.map(parsedElementToString).join();
|
||||||
|
if (result == null) {
|
||||||
|
throw MalformedValueException(key ?? '', input);
|
||||||
|
}
|
||||||
|
} on ArgumentsRequiredException catch (_) {
|
||||||
|
rethrow;
|
||||||
|
} catch (e) {
|
||||||
|
throw MalformedValueException(key ?? '', input);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
173
packages/wyatt_i18n/lib/src/core/utils/icu_parser.dart
Normal file
173
packages/wyatt_i18n/lib/src/core/utils/icu_parser.dart
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
// Copyright (C) 2023 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// ignore_for_file: avoid_dynamic_calls, inference_failure_on_untyped_parameter
|
||||||
|
|
||||||
|
import 'package:petitparser/petitparser.dart' hide Token;
|
||||||
|
import 'package:wyatt_i18n/src/domain/entities/tokens.dart';
|
||||||
|
|
||||||
|
class IcuParser {
|
||||||
|
IcuParser() {
|
||||||
|
// There is a cycle here, so we need the explicit
|
||||||
|
// set to avoid infinite recursion.
|
||||||
|
interiorText.set(contents.plus() | empty);
|
||||||
|
}
|
||||||
|
Parser get openCurly => char('{');
|
||||||
|
|
||||||
|
Parser get closeCurly => char('}');
|
||||||
|
|
||||||
|
Parser get quotedCurly => (string("'{'") | string("'}'")).map((x) => x[1]);
|
||||||
|
|
||||||
|
Parser get icuEscapedText => quotedCurly | twoSingleQuotes;
|
||||||
|
|
||||||
|
Parser get curly => openCurly | closeCurly;
|
||||||
|
|
||||||
|
Parser get notAllowedInIcuText => curly | char('<');
|
||||||
|
|
||||||
|
Parser get icuText => notAllowedInIcuText.neg();
|
||||||
|
|
||||||
|
Parser get notAllowedInNormalText => char('{');
|
||||||
|
|
||||||
|
Parser get normalText => notAllowedInNormalText.neg();
|
||||||
|
|
||||||
|
Parser get messageText =>
|
||||||
|
(icuEscapedText | icuText).plus().flatten().map(Literal.new);
|
||||||
|
|
||||||
|
Parser get nonIcuMessageText => normalText.plus().flatten().map(Literal.new);
|
||||||
|
|
||||||
|
Parser get twoSingleQuotes => string("''").map((x) => "'");
|
||||||
|
|
||||||
|
Parser get number => digit().plus().flatten().trim().map(int.parse);
|
||||||
|
|
||||||
|
Parser get id => (letter() & (word() | char('_')).star()).flatten().trim();
|
||||||
|
|
||||||
|
Parser get comma => char(',').trim();
|
||||||
|
|
||||||
|
/// Given a list of possible keywords, return a rule that accepts any of them.
|
||||||
|
/// e.g., given ["male", "female", "other"], accept any of them.
|
||||||
|
Parser asKeywords(List<String> list) =>
|
||||||
|
list.map(string).cast<Parser>().reduce((a, b) => a | b).flatten().trim();
|
||||||
|
|
||||||
|
Parser get pluralKeyword => asKeywords(
|
||||||
|
['=0', '=1', '=2', 'zero', 'one', 'two', 'few', 'many', 'other'],
|
||||||
|
);
|
||||||
|
|
||||||
|
Parser get genderKeyword => asKeywords(['female', 'male', 'other']);
|
||||||
|
|
||||||
|
SettableParser<dynamic> interiorText = undefined();
|
||||||
|
|
||||||
|
Parser get preface => (openCurly & id & comma).map((values) => values[1]);
|
||||||
|
|
||||||
|
Parser get pluralLiteral => string('plural');
|
||||||
|
|
||||||
|
Parser get pluralClause =>
|
||||||
|
(pluralKeyword & openCurly & interiorText & closeCurly).trim().map(
|
||||||
|
(result) => Option(
|
||||||
|
result[0] as String,
|
||||||
|
List<Token>.from(
|
||||||
|
(result[2] is List ? result[2] as List : [result[2]])
|
||||||
|
.cast<Token>(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Parser get plural =>
|
||||||
|
preface & pluralLiteral & comma & pluralClause.plus() & closeCurly;
|
||||||
|
|
||||||
|
Parser get intlPlural => plural.map(
|
||||||
|
(result) => Plural(
|
||||||
|
result[0] as String,
|
||||||
|
List<Option>.from(result[3] as Iterable<dynamic>),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Parser get selectLiteral => string('select');
|
||||||
|
|
||||||
|
Parser get genderClause =>
|
||||||
|
(genderKeyword & openCurly & interiorText & closeCurly).trim().map(
|
||||||
|
(result) => Option(
|
||||||
|
result[0] as String,
|
||||||
|
List<Token>.from(
|
||||||
|
(result[2] is List ? result[2] as List : [result[2]])
|
||||||
|
.cast<Token>(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Parser get gender =>
|
||||||
|
preface & selectLiteral & comma & genderClause.plus() & closeCurly;
|
||||||
|
|
||||||
|
Parser get intlGender => gender.map(
|
||||||
|
(result) => Gender(
|
||||||
|
result[0] as String,
|
||||||
|
List<Option>.from(result[3] as Iterable<dynamic>),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Parser get selectClause =>
|
||||||
|
(id & openCurly & interiorText & closeCurly).trim().map(
|
||||||
|
(result) => Option(
|
||||||
|
result[0] as String,
|
||||||
|
List<Token>.from(
|
||||||
|
(result[2] is List ? result[2] as List : [result[2]])
|
||||||
|
.cast<Token>(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Parser get generalSelect =>
|
||||||
|
preface & selectLiteral & comma & selectClause.plus() & closeCurly;
|
||||||
|
|
||||||
|
Parser get intlSelect => generalSelect.map(
|
||||||
|
(result) => Select(
|
||||||
|
result[0] as String,
|
||||||
|
List<Option>.from(result[3] as Iterable<dynamic>),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Parser get compound => (((parameter | nonIcuMessageText).plus() &
|
||||||
|
pluralOrGenderOrSelect &
|
||||||
|
(pluralOrGenderOrSelect | parameter | nonIcuMessageText).star()) |
|
||||||
|
(pluralOrGenderOrSelect &
|
||||||
|
(pluralOrGenderOrSelect | parameter | nonIcuMessageText).plus()))
|
||||||
|
.map((result) => result.expand((x) => x is List ? x : [x]).toList());
|
||||||
|
|
||||||
|
Parser get pluralOrGenderOrSelect => intlPlural | intlGender | intlSelect;
|
||||||
|
|
||||||
|
Parser get contents => pluralOrGenderOrSelect | parameter | messageText;
|
||||||
|
|
||||||
|
Parser get simpleText =>
|
||||||
|
(nonIcuMessageText | parameter | openCurly).plus().map(
|
||||||
|
(result) => result
|
||||||
|
.map((item) => item is String ? Literal(item) : item)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Parser get empty => epsilon().map((_) => Literal(''));
|
||||||
|
|
||||||
|
Parser get parameter => (openCurly & id & closeCurly)
|
||||||
|
.map((result) => Argument(result[1] as String));
|
||||||
|
|
||||||
|
List<Token>? parse(String message) {
|
||||||
|
final parsed = (compound | pluralOrGenderOrSelect | simpleText | empty)
|
||||||
|
.map(
|
||||||
|
(result) => List<Token>.from(result is List ? result : [result]),
|
||||||
|
)
|
||||||
|
.parse(message);
|
||||||
|
|
||||||
|
return parsed.isSuccess ? parsed.value : null;
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,8 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export 'arb_parser.dart';
|
export 'arb_parser.dart';
|
||||||
|
export 'i18n_parser.dart';
|
||||||
|
export 'icu_parser.dart';
|
||||||
export 'json_parser.dart';
|
export 'json_parser.dart';
|
||||||
export 'parser.dart';
|
export 'parser.dart';
|
||||||
export 'yaml_parser.dart';
|
export 'yaml_parser.dart';
|
||||||
|
@ -16,4 +16,4 @@
|
|||||||
|
|
||||||
export 'data_sources/assets_file_data_source_impl.dart';
|
export 'data_sources/assets_file_data_source_impl.dart';
|
||||||
export 'data_sources/network_data_source_impl.dart';
|
export 'data_sources/network_data_source_impl.dart';
|
||||||
export 'repositories/i18_repository_impl.dart';
|
export 'repositories/i18n_repository_impl.dart';
|
||||||
|
@ -15,15 +15,17 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:wyatt_architecture/wyatt_architecture.dart';
|
import 'package:wyatt_architecture/wyatt_architecture.dart';
|
||||||
|
import 'package:wyatt_i18n/src/core/utils/i18n_parser.dart';
|
||||||
import 'package:wyatt_i18n/wyatt_i18n.dart';
|
import 'package:wyatt_i18n/wyatt_i18n.dart';
|
||||||
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
|
import 'package:wyatt_type_utils/wyatt_type_utils.dart' hide Option;
|
||||||
|
|
||||||
class I18RepositoryImpl extends I18nRepository {
|
class I18nRepositoryImpl extends I18nRepository {
|
||||||
const I18RepositoryImpl({
|
I18nRepositoryImpl({
|
||||||
required this.dataSource,
|
required this.dataSource,
|
||||||
}) : super();
|
}) : super();
|
||||||
|
|
||||||
final I18nDataSource dataSource;
|
final I18nDataSource dataSource;
|
||||||
|
I18n _i18n = const I18n.empty();
|
||||||
|
|
||||||
Future<I18n> _parse(
|
Future<I18n> _parse(
|
||||||
String content,
|
String content,
|
||||||
@ -81,7 +83,7 @@ class I18RepositoryImpl extends I18nRepository {
|
|||||||
() async {
|
() async {
|
||||||
final content = await dataSource.load(locale: locale);
|
final content = await dataSource.load(locale: locale);
|
||||||
|
|
||||||
return _parse(
|
return _i18n = await _parse(
|
||||||
content,
|
content,
|
||||||
parser ?? dataSource.format.parser,
|
parser ?? dataSource.format.parser,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
@ -102,7 +104,7 @@ class I18RepositoryImpl extends I18nRepository {
|
|||||||
|
|
||||||
/// Strict is always true when loading from a uri. Because
|
/// Strict is always true when loading from a uri. Because
|
||||||
/// the locale is not given and can't be inferred.
|
/// the locale is not given and can't be inferred.
|
||||||
return _parse(
|
return _i18n = await _parse(
|
||||||
content,
|
content,
|
||||||
parser ?? dataSource.format.parser,
|
parser ?? dataSource.format.parser,
|
||||||
strict: true,
|
strict: true,
|
||||||
@ -116,7 +118,15 @@ class I18RepositoryImpl extends I18nRepository {
|
|||||||
String key, [
|
String key, [
|
||||||
Map<String, dynamic> arguments = const {},
|
Map<String, dynamic> arguments = const {},
|
||||||
]) {
|
]) {
|
||||||
// TODO: implement get
|
final I18nParser parser = I18nParser(i18n: _i18n, arguments: arguments);
|
||||||
throw UnimplementedError();
|
|
||||||
|
if (_i18n.containsKey(key)) {
|
||||||
|
return Result.tryCatch<String, AppException, AppException>(
|
||||||
|
() => parser.parse(_i18n[key] as String, key: key),
|
||||||
|
(error) => error,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw KeyNotFoundException(key, arguments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,6 +24,9 @@ class I18n extends Entity {
|
|||||||
required this.data,
|
required this.data,
|
||||||
}) : super();
|
}) : super();
|
||||||
|
|
||||||
|
/// Creates an empty i18n file.
|
||||||
|
const I18n.empty() : this(locale: '', unparsedData: '', data: const {});
|
||||||
|
|
||||||
/// The locale of the i18n file.
|
/// The locale of the i18n file.
|
||||||
final String locale;
|
final String locale;
|
||||||
|
|
||||||
@ -33,13 +36,7 @@ class I18n extends Entity {
|
|||||||
/// The data of the i18n file.
|
/// The data of the i18n file.
|
||||||
final Map<String, dynamic> data;
|
final Map<String, dynamic> data;
|
||||||
|
|
||||||
String operator [](String key) {
|
bool containsKey(String key) => data.containsKey(key);
|
||||||
final value = data[key];
|
|
||||||
|
|
||||||
if (value is String) {
|
dynamic operator [](String key) => data[key];
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
throw Exception('Invalid i18n key: $key');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
52
packages/wyatt_i18n/lib/src/domain/entities/tokens.dart
Normal file
52
packages/wyatt_i18n/lib/src/domain/entities/tokens.dart
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (C) 2023 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
enum TokenType { literal, argument, plural, gender, select }
|
||||||
|
|
||||||
|
class Token {
|
||||||
|
Token(this.type, this.value);
|
||||||
|
TokenType type;
|
||||||
|
String value;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Option {
|
||||||
|
Option(this.name, this.value);
|
||||||
|
String name;
|
||||||
|
List<Token> value;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Literal extends Token {
|
||||||
|
Literal(String value) : super(TokenType.literal, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Argument extends Token {
|
||||||
|
Argument(String value) : super(TokenType.argument, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Gender extends Token {
|
||||||
|
Gender(String value, this.options) : super(TokenType.gender, value);
|
||||||
|
List<Option> options;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Plural extends Token {
|
||||||
|
Plural(String value, this.options) : super(TokenType.plural, value);
|
||||||
|
List<Option> options;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Select extends Token {
|
||||||
|
Select(String value, this.options) : super(TokenType.select, value);
|
||||||
|
List<Option> options;
|
||||||
|
}
|
@ -7,7 +7,9 @@ environment:
|
|||||||
sdk: ">=2.19.0 <3.0.0"
|
sdk: ">=2.19.0 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
collection: ^1.17.0
|
||||||
flutter: {sdk: flutter}
|
flutter: {sdk: flutter}
|
||||||
|
intl: ^0.18.0
|
||||||
path: ^1.8.0
|
path: ^1.8.0
|
||||||
petitparser: ^5.1.0
|
petitparser: ^5.1.0
|
||||||
wyatt_architecture:
|
wyatt_architecture:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user