diff --git a/packages/wyatt_i18n/lib/src/data/data_sources/network_data_source_impl.dart b/packages/wyatt_i18n/lib/src/data/data_sources/network_data_source_impl.dart
index 64280681..2e683e9f 100644
--- a/packages/wyatt_i18n/lib/src/data/data_sources/network_data_source_impl.dart
+++ b/packages/wyatt_i18n/lib/src/data/data_sources/network_data_source_impl.dart
@@ -14,21 +14,117 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+import 'package:flutter/services.dart';
+import 'package:http/http.dart' as http;
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 network_data_source_impl}
+/// Implementation of [I18nDataSource] that loads i18n files from the network.
+///
+/// The [baseUri] is the base uri where the i18n files are located.
+/// The [baseName] is the name of the i18n files without the extension.
+/// The [fallbackAssetPath] is the path to the fallback i18n files.
+/// The [format] is the format of the i18n files.
+///
+/// For example, if the i18n files are located at `https://example.com/i18n/`
+/// and are named `i18n.en.arb`, `i18n.fr.arb`, etc., then the [baseUri] is
+/// `https://example.com/i18n/` and the [baseName] is `i18n` and the
+/// [format] is [Format.arb].
+///
+/// If the i18n file for the given locale is not found or if the fetch fails,
+/// then the fallback i18n files are loaded from the [fallbackAssetPath] in
+/// the root bundle.
+///
+/// For example, if the fallback i18n files are located in the `assets` and are
+/// named `i18n.arb`, `i18n.en.arb`, `i18n.fr.arb`, etc., then the
+/// [fallbackAssetPath] is `assets`.
+/// {@endtemplate}
class NetworkDataSourceImpl extends I18nDataSource {
- const NetworkDataSourceImpl({super.format = Format.arb}) : super();
-
- @override
- Future load({required String? locale}) {
- // TODO(wyatt): implement load
- throw UnimplementedError();
+ /// {@macro network_data_source_impl}
+ const NetworkDataSourceImpl({
+ required this.baseUri,
+ this.baseName = 'i18n',
+ this.fallbackAssetPath = 'assets',
+ super.format = Format.arb,
+ super.defaultLocale = 'en',
+ }) : super();
+
+ /// The base uri where the i18n files are located.
+ final Uri baseUri;
+
+ /// The name of the i18n files without the extension.
+ final String baseName;
+
+ /// The path to the fallback i18n files.
+ ///
+ /// This is used when the i18n file for the given locale is not found or
+ /// if the fetch fails.
+ final String fallbackAssetPath;
+
+ /// Tries to load the i18n file from the given [locale].
+ Future _tryLoad(String? locale, {Uri? overrideUri}) async {
+ String? content;
+ final ext = format.name;
+
+ /// If the locale is null, then we try to load the default i18n file from
+ /// the fallback asset path.
+ /// Otherwise, we try to load the i18n file for the given locale from the
+ /// base uri.
+ final path = AssetsUtils.cleanPath(
+ locale == null
+ ? '$fallbackAssetPath/$baseName.$defaultLocale.$ext'
+ : overrideUri?.toString() ?? '$baseUri/$baseName.$locale.$ext',
+ );
+
+ if (locale == null) {
+ /// Load the default i18n file from the fallback asset path.
+ try {
+ if (await AssetsUtils.assetExists(path)) {
+ content = await rootBundle.loadString(path);
+ }
+ } catch (_) {
+ content = null;
+ }
+ } else {
+ /// Load the i18n file for the given locale from the base uri.
+ try {
+ final response = await http.get(Uri.parse(path));
+ if (response.statusCode >= 200 && response.statusCode < 300) {
+ content = response.body;
+ }
+ } catch (_) {
+ content = null;
+ }
+ }
+
+ /// If the i18n file is not found, then we try to load the
+ /// default i18n file.
+ if (content == null && locale != null) {
+ try {
+ final fallbackPath = AssetsUtils.cleanPath(
+ '$fallbackAssetPath/$baseName.$defaultLocale.$ext',
+ );
+ content = await rootBundle.loadString(fallbackPath);
+ } catch (_) {
+ throw SourceNotFoundException(path, format: format);
+ }
+ }
+
+ /// If the default i18n file is not found, then we throw an exception.
+ /// This case should happen only if the locale is null.
+ if (content == null) {
+ throw SourceNotFoundException(path, format: format);
+ }
+
+ return content;
}
-
+
@override
- Future loadFrom(Uri uri) {
- // TODO(wyatt): implement loadFrom
- throw UnimplementedError();
- }
+ Future load({required String? locale}) async => _tryLoad(locale);
+
+ @override
+ Future loadFrom(Uri uri) async => _tryLoad(null, overrideUri: uri);
}
diff --git a/packages/wyatt_i18n/pubspec.yaml b/packages/wyatt_i18n/pubspec.yaml
index b04875e8..6bb76e8e 100644
--- a/packages/wyatt_i18n/pubspec.yaml
+++ b/packages/wyatt_i18n/pubspec.yaml
@@ -9,6 +9,7 @@ environment:
dependencies:
collection: ^1.17.0
flutter: {sdk: flutter}
+ http: ^0.13.5
intl: ^0.18.0
path: ^1.8.0
petitparser: ^5.1.0