From 05fe7fb2b964030cb7d159fc75efe63c8b9f07fd Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Tue, 28 Feb 2023 11:22:23 +0100 Subject: [PATCH] feat(i18n): implements network data source --- .../network_data_source_impl.dart | 118 ++++++++++++++++-- packages/wyatt_i18n/pubspec.yaml | 1 + 2 files changed, 108 insertions(+), 11 deletions(-) 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