diff --git a/packages/wyatt_i18n/example/lib/main.dart b/packages/wyatt_i18n/example/lib/main.dart
index ed462ce9..82ac6a5b 100644
--- a/packages/wyatt_i18n/example/lib/main.dart
+++ b/packages/wyatt_i18n/example/lib/main.dart
@@ -43,14 +43,30 @@ class App extends StatelessWidget {
// test
const I18nDataSource dataSource = AssetsFileDataSourceImpl();
- const I18nRepository repository =
- I18RepositoryImpl(dataSource: dataSource);
-
- final test = (await repository.load('en')).ok;
+ final I18nRepository repository =
+ I18nRepositoryImpl(dataSource: dataSource);
- print(test?.locale);
- print(test?.data);
- print(test?.data['btnAddFile']);
+ final test1 = (await repository.load(locale: 'en')).ok;
+ // final test2 = (await repository.loadFrom(
+ // Uri.parse('assets/i18n.en.yaml'),
+ // parser: const YamlParser(),
+ // ))
+ // .ok;
+
+ // print(test1?.locale);
+ // print(test1?.data);
+ // 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';
}),
diff --git a/packages/wyatt_i18n/lib/src/core/utils/i18n_parser.dart b/packages/wyatt_i18n/lib/src/core/utils/i18n_parser.dart
new file mode 100644
index 00000000..6cfb1273
--- /dev/null
+++ b/packages/wyatt_i18n/lib/src/core/utils/i18n_parser.dart
@@ -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 .
+
+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 {
+ const I18nParser({
+ required this.i18n,
+ this.arguments = const {},
+ }) : super();
+
+ final I18n i18n;
+ final Map arguments;
+
+ dynamic getArgument(String key) {
+ final arg = arguments[key];
+ if (arg == null) {
+ throw ArgumentsRequiredException(key, 'value');
+ }
+
+ return arg;
+ }
+
+ String pluralToString(Plural token) {
+ final List 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 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 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;
+ }
+}
diff --git a/packages/wyatt_i18n/lib/src/core/utils/icu_parser.dart b/packages/wyatt_i18n/lib/src/core/utils/icu_parser.dart
new file mode 100644
index 00000000..8405642a
--- /dev/null
+++ b/packages/wyatt_i18n/lib/src/core/utils/icu_parser.dart
@@ -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 .
+
+// 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 list) =>
+ list.map(string).cast().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 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.from(
+ (result[2] is List ? result[2] as List : [result[2]])
+ .cast(),
+ ),
+ ),
+ );
+
+ Parser get plural =>
+ preface & pluralLiteral & comma & pluralClause.plus() & closeCurly;
+
+ Parser get intlPlural => plural.map(
+ (result) => Plural(
+ result[0] as String,
+ List.from(result[3] as Iterable),
+ ),
+ );
+
+ Parser get selectLiteral => string('select');
+
+ Parser get genderClause =>
+ (genderKeyword & openCurly & interiorText & closeCurly).trim().map(
+ (result) => Option(
+ result[0] as String,
+ List.from(
+ (result[2] is List ? result[2] as List : [result[2]])
+ .cast(),
+ ),
+ ),
+ );
+
+ Parser get gender =>
+ preface & selectLiteral & comma & genderClause.plus() & closeCurly;
+
+ Parser get intlGender => gender.map(
+ (result) => Gender(
+ result[0] as String,
+ List.from(result[3] as Iterable),
+ ),
+ );
+
+ Parser get selectClause =>
+ (id & openCurly & interiorText & closeCurly).trim().map(
+ (result) => Option(
+ result[0] as String,
+ List.from(
+ (result[2] is List ? result[2] as List : [result[2]])
+ .cast(),
+ ),
+ ),
+ );
+
+ Parser get generalSelect =>
+ preface & selectLiteral & comma & selectClause.plus() & closeCurly;
+
+ Parser get intlSelect => generalSelect.map(
+ (result) => Select(
+ result[0] as String,
+ List.from(result[3] as Iterable),
+ ),
+ );
+
+ 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? parse(String message) {
+ final parsed = (compound | pluralOrGenderOrSelect | simpleText | empty)
+ .map(
+ (result) => List.from(result is List ? result : [result]),
+ )
+ .parse(message);
+
+ return parsed.isSuccess ? parsed.value : null;
+ }
+}
diff --git a/packages/wyatt_i18n/lib/src/core/utils/utils.dart b/packages/wyatt_i18n/lib/src/core/utils/utils.dart
index 6dc64f48..e775a5b7 100644
--- a/packages/wyatt_i18n/lib/src/core/utils/utils.dart
+++ b/packages/wyatt_i18n/lib/src/core/utils/utils.dart
@@ -1,20 +1,22 @@
// 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 .
export 'arb_parser.dart';
+export 'i18n_parser.dart';
+export 'icu_parser.dart';
export 'json_parser.dart';
export 'parser.dart';
export 'yaml_parser.dart';
diff --git a/packages/wyatt_i18n/lib/src/data/data.dart b/packages/wyatt_i18n/lib/src/data/data.dart
index aa424ac5..2bd813a2 100644
--- a/packages/wyatt_i18n/lib/src/data/data.dart
+++ b/packages/wyatt_i18n/lib/src/data/data.dart
@@ -16,4 +16,4 @@
export 'data_sources/assets_file_data_source_impl.dart';
export 'data_sources/network_data_source_impl.dart';
-export 'repositories/i18_repository_impl.dart';
+export 'repositories/i18n_repository_impl.dart';
diff --git a/packages/wyatt_i18n/lib/src/data/repositories/i18_repository_impl.dart b/packages/wyatt_i18n/lib/src/data/repositories/i18n_repository_impl.dart
similarity index 84%
rename from packages/wyatt_i18n/lib/src/data/repositories/i18_repository_impl.dart
rename to packages/wyatt_i18n/lib/src/data/repositories/i18n_repository_impl.dart
index 3bd2a38d..4026581b 100644
--- a/packages/wyatt_i18n/lib/src/data/repositories/i18_repository_impl.dart
+++ b/packages/wyatt_i18n/lib/src/data/repositories/i18n_repository_impl.dart
@@ -15,15 +15,17 @@
// along with this program. If not, see .
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_type_utils/wyatt_type_utils.dart';
+import 'package:wyatt_type_utils/wyatt_type_utils.dart' hide Option;
-class I18RepositoryImpl extends I18nRepository {
- const I18RepositoryImpl({
+class I18nRepositoryImpl extends I18nRepository {
+ I18nRepositoryImpl({
required this.dataSource,
}) : super();
final I18nDataSource dataSource;
+ I18n _i18n = const I18n.empty();
Future _parse(
String content,
@@ -81,7 +83,7 @@ class I18RepositoryImpl extends I18nRepository {
() async {
final content = await dataSource.load(locale: locale);
- return _parse(
+ return _i18n = await _parse(
content,
parser ?? dataSource.format.parser,
locale: locale,
@@ -102,7 +104,7 @@ class I18RepositoryImpl extends I18nRepository {
/// Strict is always true when loading from a uri. Because
/// the locale is not given and can't be inferred.
- return _parse(
+ return _i18n = await _parse(
content,
parser ?? dataSource.format.parser,
strict: true,
@@ -116,7 +118,15 @@ class I18RepositoryImpl extends I18nRepository {
String key, [
Map arguments = const {},
]) {
- // TODO: implement get
- throw UnimplementedError();
+ final I18nParser parser = I18nParser(i18n: _i18n, arguments: arguments);
+
+ if (_i18n.containsKey(key)) {
+ return Result.tryCatch(
+ () => parser.parse(_i18n[key] as String, key: key),
+ (error) => error,
+ );
+ } else {
+ throw KeyNotFoundException(key, arguments);
+ }
}
}
diff --git a/packages/wyatt_i18n/lib/src/domain/entities/i18n.dart b/packages/wyatt_i18n/lib/src/domain/entities/i18n.dart
index e09c06c4..04fdbbf5 100644
--- a/packages/wyatt_i18n/lib/src/domain/entities/i18n.dart
+++ b/packages/wyatt_i18n/lib/src/domain/entities/i18n.dart
@@ -24,6 +24,9 @@ class I18n extends Entity {
required this.data,
}) : super();
+ /// Creates an empty i18n file.
+ const I18n.empty() : this(locale: '', unparsedData: '', data: const {});
+
/// The locale of the i18n file.
final String locale;
@@ -33,13 +36,7 @@ class I18n extends Entity {
/// The data of the i18n file.
final Map data;
- String operator [](String key) {
- final value = data[key];
+ bool containsKey(String key) => data.containsKey(key);
- if (value is String) {
- return value;
- } else {
- throw Exception('Invalid i18n key: $key');
- }
- }
+ dynamic operator [](String key) => data[key];
}
diff --git a/packages/wyatt_i18n/lib/src/domain/entities/tokens.dart b/packages/wyatt_i18n/lib/src/domain/entities/tokens.dart
new file mode 100644
index 00000000..048a9e7d
--- /dev/null
+++ b/packages/wyatt_i18n/lib/src/domain/entities/tokens.dart
@@ -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 .
+
+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 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 options;
+}
+
+class Plural extends Token {
+ Plural(String value, this.options) : super(TokenType.plural, value);
+ List options;
+}
+
+class Select extends Token {
+ Select(String value, this.options) : super(TokenType.select, value);
+ List options;
+}
diff --git a/packages/wyatt_i18n/pubspec.yaml b/packages/wyatt_i18n/pubspec.yaml
index 9124a7cd..b04875e8 100644
--- a/packages/wyatt_i18n/pubspec.yaml
+++ b/packages/wyatt_i18n/pubspec.yaml
@@ -7,7 +7,9 @@ environment:
sdk: ">=2.19.0 <3.0.0"
dependencies:
+ collection: ^1.17.0
flutter: {sdk: flutter}
+ intl: ^0.18.0
path: ^1.8.0
petitparser: ^5.1.0
wyatt_architecture: