From 96ba4adc3ceeef738e7d56d1e00f2de99ba226be Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Tue, 28 Feb 2023 11:22:04 +0100 Subject: [PATCH] feat(i18n): add default locale --- .../lib/src/core/utils/assets_utils.dart | 40 +++++++++++++++++ .../assets_file_data_source_impl.dart | 43 +++++++++---------- .../domain/data_sources/i18n_data_source.dart | 16 +++++-- 3 files changed, 73 insertions(+), 26 deletions(-) create mode 100644 packages/wyatt_i18n/lib/src/core/utils/assets_utils.dart 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); }