diff --git a/packages/wyatt_i18n/lib/src/core/utils/assets_utils.dart b/packages/wyatt_i18n/lib/src/core/utils/assets_utils.dart
new file mode 100644
index 00000000..f1ea9ebc
--- /dev/null
+++ b/packages/wyatt_i18n/lib/src/core/utils/assets_utils.dart
@@ -0,0 +1,40 @@
+// 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 'dart:convert';
+
+import 'package:flutter/services.dart';
+
+class AssetsUtils {
+ /// Checks if the given [assetPath] is a local asset.
+ ///
+ /// In fact, this method loads the asset and checks if it is null.
+ static Future assetExists(String assetPath) async {
+ final encoded = utf8.encoder.convert(
+ Uri(path: Uri.encodeFull(assetPath)).path,
+ );
+ final asset = await ServicesBinding.instance.defaultBinaryMessenger
+ .send('flutter/assets', encoded.buffer.asByteData());
+
+ return asset != null;
+ }
+
+ /// Cleans the given [path] by removing all the consecutive slashes.
+ /// For example, `///a///b///c///` becomes `/a/b/c/`.
+ /// It also removes the first slash if it exists.
+ static String cleanPath(String path) =>
+ path.replaceAll(RegExp(r'\/+'), '/').replaceFirst(RegExp(r'^\/'), '');
+}
diff --git a/packages/wyatt_i18n/lib/src/data/data_sources/assets_file_data_source_impl.dart b/packages/wyatt_i18n/lib/src/data/data_sources/assets_file_data_source_impl.dart
index eb2a69f2..5d531826 100644
--- a/packages/wyatt_i18n/lib/src/data/data_sources/assets_file_data_source_impl.dart
+++ b/packages/wyatt_i18n/lib/src/data/data_sources/assets_file_data_source_impl.dart
@@ -14,11 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-import 'dart:convert';
-
-import 'package:flutter/services.dart' show ServicesBinding, rootBundle;
+import 'package:flutter/services.dart' show AssetBundle, rootBundle;
import 'package:wyatt_i18n/src/core/enums/format.dart';
import 'package:wyatt_i18n/src/core/exceptions/exceptions.dart';
+import 'package:wyatt_i18n/src/core/utils/assets_utils.dart';
import 'package:wyatt_i18n/src/domain/data_sources/i18n_data_source.dart';
/// {@template assets_file_data_source_impl}
@@ -34,10 +33,11 @@ import 'package:wyatt_i18n/src/domain/data_sources/i18n_data_source.dart';
/// {@endtemplate}
class AssetsFileDataSourceImpl extends I18nDataSource {
/// {@macro assets_file_data_source_impl}
- const AssetsFileDataSourceImpl({
+ AssetsFileDataSourceImpl({
this.basePath = 'assets',
this.baseName = 'i18n',
super.format = Format.arb,
+ super.defaultLocale = 'en',
}) : super();
/// The folder where the i18n files are located.
@@ -46,29 +46,23 @@ class AssetsFileDataSourceImpl extends I18nDataSource {
/// The name of the i18n files without the extension.
final String baseName;
- /// Checks if the given [assetPath] is a local asset.
- ///
- /// In fact, this method loads the asset and checks if it is null.
- Future assetExists(String assetPath) async {
- final encoded = utf8.encoder.convert(
- Uri(path: Uri.encodeFull(assetPath)).path,
- );
- final asset = await ServicesBinding.instance.defaultBinaryMessenger
- .send('flutter/assets', encoded.buffer.asByteData());
-
- return asset != null;
- }
+ /// The asset bundle used to load the i18n files.
+ final AssetBundle assetBundle = rootBundle;
/// Tries to load the i18n file from the given [locale].
Future _tryLoad(String? locale) async {
String? content;
final ext = format.name;
- final path = locale == null
- ? '$basePath/$baseName.$ext'
- : '$basePath/$baseName.$locale.$ext';
+
+ final path = AssetsUtils.cleanPath(
+ locale == null
+ ? '$basePath/$baseName.$defaultLocale.$ext'
+ : '$basePath/$baseName.$locale.$ext',
+ );
+
try {
- if (await assetExists(path)) {
- content = await rootBundle.loadString(path);
+ if (await AssetsUtils.assetExists(path)) {
+ content = await assetBundle.loadString(path);
}
} catch (_) {
content = null;
@@ -78,7 +72,10 @@ class AssetsFileDataSourceImpl extends I18nDataSource {
/// default i18n file.
if (content == null && locale != null) {
try {
- content = await rootBundle.loadString('$basePath/$baseName.$ext');
+ final fallbackPath = AssetsUtils.cleanPath(
+ '$basePath/$baseName.$defaultLocale.$ext',
+ );
+ content = await assetBundle.loadString(fallbackPath);
} catch (_) {
throw SourceNotFoundException(path, format: format);
}
@@ -98,7 +95,7 @@ class AssetsFileDataSourceImpl extends I18nDataSource {
Future _tryLoadUri(Uri uri) async {
String? content;
try {
- content = await rootBundle.loadString(uri.toString());
+ content = await assetBundle.loadString(uri.toString());
} catch (_) {
throw SourceNotFoundException(uri.toString(), format: format);
}
diff --git a/packages/wyatt_i18n/lib/src/domain/data_sources/i18n_data_source.dart b/packages/wyatt_i18n/lib/src/domain/data_sources/i18n_data_source.dart
index 3421387b..942e35b9 100644
--- a/packages/wyatt_i18n/lib/src/domain/data_sources/i18n_data_source.dart
+++ b/packages/wyatt_i18n/lib/src/domain/data_sources/i18n_data_source.dart
@@ -17,23 +17,33 @@
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_i18n/src/core/enums/format.dart';
+/// {@template i18n_data_source}
/// Base class for i18n data sources.
-///
+///
/// This class is used to load i18n files from a source.
/// It returns a [Future] that resolves to a [String] containing the i18n file
/// contents.
+/// {@endtemplate}
abstract class I18nDataSource extends BaseDataSource {
- const I18nDataSource({this.format = Format.arb});
+ /// {@macro i18n_data_source}
+ const I18nDataSource({
+ this.format = Format.arb,
+ this.defaultLocale = 'en',
+ });
+ /// The format of the i18n file.
final Format format;
+ /// The default locale.
+ final String defaultLocale;
+
/// Loads the i18n file from the source.
/// If [locale] is not `null`, it will load the file with the given [locale].
/// Otherwise, it will load the file from the default location.
Future load({required String? locale});
/// Loads the i18n file from the source.
- ///
+ ///
/// This method is used to load the i18n file from the given [Uri].
Future loadFrom(Uri uri);
}