Compare commits
	
		
			3 Commits
		
	
	
		
			55ee89fb26
			...
			dbe8554eb7
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| dbe8554eb7 | |||
| 70c6d91310 | |||
| 6c7e561fde | 
@ -23,28 +23,131 @@
 | 
				
			|||||||
  <img src="https://img.shields.io/badge/SDK-Flutter-blue?style=flat-square" alt="SDK: Flutter" />
 | 
					  <img src="https://img.shields.io/badge/SDK-Flutter-blue?style=flat-square" alt="SDK: Flutter" />
 | 
				
			||||||
</p>
 | 
					</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A package by wyatt studio
 | 
					This package aims to facilitate and improve the internationalization of your applications. It allows, among other things, to load translation files on the fly during the execution of the application.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Features
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TODO: List what your package can do. Maybe include images, gifs, or videos.
 | 
					* Load translation files
 | 
				
			||||||
 | 
					  + [x] Load translation files from assets
 | 
				
			||||||
 | 
					  + [x] Load translation files from the network
 | 
				
			||||||
 | 
					  + [ ] Load translation files from the file system
 | 
				
			||||||
 | 
					  + [ ] Load translation files from multiple sources
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Getting started
 | 
					* Supports multiple formats
 | 
				
			||||||
 | 
					  + [x] Supports JSON format
 | 
				
			||||||
 | 
					  + [x] Supports YAML format
 | 
				
			||||||
 | 
					  + [x] Supports ARB formats
 | 
				
			||||||
 | 
					  + [ ] Supports CSV format
 | 
				
			||||||
 | 
					  + [x] Supports custom formats parsers (see Parser class)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TODO: List prerequisites and provide or point to information on how to
 | 
					* Usage
 | 
				
			||||||
start using the package.
 | 
					  + [x] Detects the current locale
 | 
				
			||||||
 | 
					  + [x] Act as a LocalizationDelegate
 | 
				
			||||||
 | 
					  + [x] Act as a DataSource (in the sense of the Wyatt Architecture)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Other
 | 
				
			||||||
 | 
					  + [ ] Generate translation constants from fallback translation files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Usage
 | 
					## Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TODO: Include short and useful examples for package users. Add longer examples
 | 
					You can use this package as a LocalizationDelegate or as a DataSource.
 | 
				
			||||||
to `/example` folder.
 | 
					
 | 
				
			||||||
 | 
					### As a LocalizationDelegate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It is recommended to use this package as a LocalizationDelegate. This allows you to use the `context.i18n` method to translate your strings. It follows the standard of the `flutter_localizations` package and you can use it in the MaterialApp widget as follows:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```dart
 | 
					```dart
 | 
				
			||||||
const like = 'sample';
 | 
					class MyApp extends StatelessWidget {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return MaterialApp(
 | 
				
			||||||
 | 
					      title: 'Flutter Demo',
 | 
				
			||||||
 | 
					      theme: ThemeData(
 | 
				
			||||||
 | 
					        primarySwatch: Colors.blue,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      localizationsDelegates: [
 | 
				
			||||||
 | 
					        I18nDelegate(
 | 
				
			||||||
 | 
					          dataSource: NetworkI18nDataSourceImpl(
 | 
				
			||||||
 | 
					            baseUri: 'https://i18n.wyatt-studio.fr/apps/flutter_demo',
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          localeTransformer: (locale) => locale.languageCode,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        GlobalMaterialLocalizations.delegate,
 | 
				
			||||||
 | 
					        GlobalWidgetsLocalizations.delegate
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      supportedLocales: [
 | 
				
			||||||
 | 
					        Locale('en'),
 | 
				
			||||||
 | 
					        Locale('fr'),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      home: MyHomePage(title: 'Flutter Demo Home Page'),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Additional information
 | 
					And in your widgets:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TODO: Tell users more about the package: where to find more information, how to
 | 
					```dart
 | 
				
			||||||
contribute to the package, how to file issues, what response they can expect
 | 
					Text(context.i18n('youHavePushed', {'count': 42})),
 | 
				
			||||||
from the package authors, and more.
 | 
					// => 'You have pushed the button this many times: 42' in English
 | 
				
			||||||
 | 
					// => 'Vous avez appuyé sur le bouton ce nombre de fois: 42' in French
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### As a DataSource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This gives you more control over the internationalization of your application. You can handle the loading of the translation files yourself.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For example, if you want to create a Repository that will handle the loading of the translation files, you can do it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Provide DataSource with GetIt:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```dart
 | 
				
			||||||
 | 
					// Initialize i18n
 | 
				
			||||||
 | 
					final I18nDataSource i18nDataSource =
 | 
				
			||||||
 | 
					    await AssetsI18nDataSourceImpl.withSystemLocale(
 | 
				
			||||||
 | 
					  basePath: 'l10n',
 | 
				
			||||||
 | 
					  baseName: 'intl',
 | 
				
			||||||
 | 
					  localeTransformer: (locale) => locale.languageCode,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Initialize real sources/services
 | 
				
			||||||
 | 
					GetIt.I.registerLazySingleton<I18nDataSource>(
 | 
				
			||||||
 | 
					  () => i18nDataSource,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Create a Repository:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```dart
 | 
				
			||||||
 | 
					class I18nRepository {
 | 
				
			||||||
 | 
					  I18nRepository({
 | 
				
			||||||
 | 
					    required this.dataSource,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  final I18nDataSource dataSource;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<I18n> load() async {
 | 
				
			||||||
 | 
					    final i18n = await dataSource.load();
 | 
				
			||||||
 | 
					    return i18n;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					And use it in your cubit:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```dart
 | 
				
			||||||
 | 
					class MyCubit extends Cubit<MyState> {
 | 
				
			||||||
 | 
					  MyCubit({
 | 
				
			||||||
 | 
					    required this.i18nRepository,
 | 
				
			||||||
 | 
					  }) : super(MyState());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final I18nRepository i18nRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> loadI18n() async {
 | 
				
			||||||
 | 
					    final i18n = await i18nRepository.load();
 | 
				
			||||||
 | 
					    emit(state.copyWith(i18n: i18n));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> Note: you should create a cache system to avoid reloading the translation files every time.
 | 
				
			||||||
 | 
				
			|||||||
@ -29,36 +29,32 @@ class App extends StatelessWidget {
 | 
				
			|||||||
  static const String title = 'Wyatt i18n Example';
 | 
					  static const String title = 'Wyatt i18n Example';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) => MaterialApp(
 | 
				
			||||||
    final I18nDataSource dataSource = NetworkDataSourceImpl(
 | 
					        title: title,
 | 
				
			||||||
      baseUri: Uri.parse(
 | 
					        theme: ThemeData(
 | 
				
			||||||
        'https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/raw/commit/75f561a19e0484e67e511dbf29601ec5f58544aa/packages/wyatt_i18n/example/assets/',
 | 
					          primarySwatch: Colors.blue,
 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    final I18nRepository repository =
 | 
					 | 
				
			||||||
        I18nRepositoryImpl(dataSource: dataSource);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return MaterialApp(
 | 
					 | 
				
			||||||
      title: title,
 | 
					 | 
				
			||||||
      theme: ThemeData(
 | 
					 | 
				
			||||||
        primarySwatch: Colors.blue,
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
      localizationsDelegates: [
 | 
					 | 
				
			||||||
        I18nDelegate(repository: repository),
 | 
					 | 
				
			||||||
        GlobalMaterialLocalizations.delegate,
 | 
					 | 
				
			||||||
        GlobalWidgetsLocalizations.delegate
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
      home: Scaffold(
 | 
					 | 
				
			||||||
        appBar: AppBar(
 | 
					 | 
				
			||||||
          title: const Text(title),
 | 
					 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        body: Builder(
 | 
					        localizationsDelegates: [
 | 
				
			||||||
          builder: (ctx) => Center(
 | 
					          I18nDelegate(
 | 
				
			||||||
            child: Text(ctx.i18n('youHavePushed', {'count': 654})),
 | 
					            dataSource: NetworkI18nDataSourceImpl(
 | 
				
			||||||
 | 
					              baseUri: Uri.parse(
 | 
				
			||||||
 | 
					                'https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/raw/commit/75f561a19e0484e67e511dbf29601ec5f58544aa/packages/wyatt_i18n/example/assets/',
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              localeTransformer: (locale) => locale.languageCode,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          GlobalMaterialLocalizations.delegate,
 | 
				
			||||||
 | 
					          GlobalWidgetsLocalizations.delegate
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        home: Scaffold(
 | 
				
			||||||
 | 
					          appBar: AppBar(
 | 
				
			||||||
 | 
					            title: const Text(title),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          body: Builder(
 | 
				
			||||||
 | 
					            builder: (ctx) => Center(
 | 
				
			||||||
 | 
					              child: Text(ctx.i18n('youHavePushed', {'count': 654})),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      );
 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -14,11 +14,10 @@
 | 
				
			|||||||
// You should have received a copy of the GNU General Public License
 | 
					// You should have received a copy of the GNU General Public License
 | 
				
			||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
					// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:wyatt_i18n/src/core/utils/arb_parser.dart';
 | 
					 | 
				
			||||||
import 'package:wyatt_i18n/src/core/utils/json_parser.dart';
 | 
					 | 
				
			||||||
import 'package:wyatt_i18n/src/core/utils/parser.dart';
 | 
					import 'package:wyatt_i18n/src/core/utils/parser.dart';
 | 
				
			||||||
 | 
					import 'package:wyatt_i18n/src/core/utils/parsers/arb_parser.dart';
 | 
				
			||||||
import 'package:wyatt_i18n/src/core/utils/yaml_parser.dart';
 | 
					import 'package:wyatt_i18n/src/core/utils/parsers/json_parser.dart';
 | 
				
			||||||
 | 
					import 'package:wyatt_i18n/src/core/utils/parsers/yaml_parser.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Enum for i18n file formats and extensions.
 | 
					/// Enum for i18n file formats and extensions.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										24
									
								
								packages/wyatt_i18n/lib/src/core/utils/intl_utils.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								packages/wyatt_i18n/lib/src/core/utils/intl_utils.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					// 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 <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:intl/intl_standalone.dart'
 | 
				
			||||||
 | 
					    if (dart.library.html) 'package:intl/intl_browser.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Utility class for internationalization.
 | 
				
			||||||
 | 
					abstract class IntlUtils {
 | 
				
			||||||
 | 
					  /// Returns system locale.
 | 
				
			||||||
 | 
					  static Future<String> getSystemLocale() => findSystemLocale();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -14,8 +14,8 @@
 | 
				
			|||||||
// You should have received a copy of the GNU General Public License
 | 
					// You should have received a copy of the GNU General Public License
 | 
				
			||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
					// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:wyatt_i18n/src/core/utils/json_parser.dart';
 | 
					 | 
				
			||||||
import 'package:wyatt_i18n/src/core/utils/parser.dart';
 | 
					import 'package:wyatt_i18n/src/core/utils/parser.dart';
 | 
				
			||||||
 | 
					import 'package:wyatt_i18n/src/core/utils/parsers/json_parser.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// {@template arb_parser}
 | 
					/// {@template arb_parser}
 | 
				
			||||||
/// A class that parses a given input of type [String] into a given output
 | 
					/// A class that parses a given input of type [String] into a given output
 | 
				
			||||||
@ -20,8 +20,8 @@ import 'package:wyatt_i18n/src/domain/entities/tokens.dart';
 | 
				
			|||||||
import 'package:wyatt_i18n/wyatt_i18n.dart';
 | 
					import 'package:wyatt_i18n/wyatt_i18n.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// {@template i18n_file_parser}
 | 
					/// {@template i18n_file_parser}
 | 
				
			||||||
/// This class is responsible for parsing the [I18nFile] and returning the
 | 
					/// This class is responsible for parsing the [I18n] and returning the
 | 
				
			||||||
/// translated string.
 | 
					/// translated string using the [arguments] provided and the [IcuParser].
 | 
				
			||||||
/// {@endtemplate}
 | 
					/// {@endtemplate}
 | 
				
			||||||
class I18nFileParser extends Parser<String, String> {
 | 
					class I18nFileParser extends Parser<String, String> {
 | 
				
			||||||
  /// {@macro i18n_file_parser}
 | 
					  /// {@macro i18n_file_parser}
 | 
				
			||||||
@ -30,8 +30,8 @@ class I18nFileParser extends Parser<String, String> {
 | 
				
			|||||||
    this.arguments = const {},
 | 
					    this.arguments = const {},
 | 
				
			||||||
  }) : super();
 | 
					  }) : super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// The [I18nFile] to be parsed.
 | 
					  /// The [I18n] to be parsed.
 | 
				
			||||||
  final I18nFile i18n;
 | 
					  final I18n i18n;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// The arguments to be used in the translation.
 | 
					  /// The arguments to be used in the translation.
 | 
				
			||||||
  final Map<String, dynamic> arguments;
 | 
					  final Map<String, dynamic> arguments;
 | 
				
			||||||
@ -17,6 +17,7 @@
 | 
				
			|||||||
// ignore_for_file: avoid_dynamic_calls, inference_failure_on_untyped_parameter
 | 
					// ignore_for_file: avoid_dynamic_calls, inference_failure_on_untyped_parameter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:petitparser/petitparser.dart' hide Token;
 | 
					import 'package:petitparser/petitparser.dart' hide Token;
 | 
				
			||||||
 | 
					import 'package:wyatt_i18n/src/core/utils/parser.dart' as wyatt;
 | 
				
			||||||
import 'package:wyatt_i18n/src/domain/entities/tokens.dart';
 | 
					import 'package:wyatt_i18n/src/domain/entities/tokens.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// {@template icu_parser}
 | 
					/// {@template icu_parser}
 | 
				
			||||||
@ -24,7 +25,7 @@ import 'package:wyatt_i18n/src/domain/entities/tokens.dart';
 | 
				
			|||||||
/// See https://unicode-org.github.io/icu/userguide/format_parse/messages/
 | 
					/// See https://unicode-org.github.io/icu/userguide/format_parse/messages/
 | 
				
			||||||
/// for the syntax.
 | 
					/// for the syntax.
 | 
				
			||||||
/// {@endtemplate}
 | 
					/// {@endtemplate}
 | 
				
			||||||
class IcuParser {
 | 
					class IcuParser extends wyatt.Parser<String, List<Token>?> {
 | 
				
			||||||
  /// {@macro icu_parser}
 | 
					  /// {@macro icu_parser}
 | 
				
			||||||
  IcuParser() {
 | 
					  IcuParser() {
 | 
				
			||||||
    // There is a cycle here, so we need the explicit
 | 
					    // There is a cycle here, so we need the explicit
 | 
				
			||||||
@ -167,12 +168,13 @@ class IcuParser {
 | 
				
			|||||||
  Parser get parameter => (openCurly & id & closeCurly)
 | 
					  Parser get parameter => (openCurly & id & closeCurly)
 | 
				
			||||||
      .map((result) => Argument(result[1] as String));
 | 
					      .map((result) => Argument(result[1] as String));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  List<Token>? parse(String message) {
 | 
					  @override
 | 
				
			||||||
 | 
					  List<Token>? parse(String input) {
 | 
				
			||||||
    final parsed = (compound | pluralOrGenderOrSelect | simpleText | empty)
 | 
					    final parsed = (compound | pluralOrGenderOrSelect | simpleText | empty)
 | 
				
			||||||
        .map(
 | 
					        .map(
 | 
				
			||||||
          (result) => List<Token>.from(result is List ? result : [result]),
 | 
					          (result) => List<Token>.from(result is List ? result : [result]),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .parse(message);
 | 
					        .parse(input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return parsed.isSuccess ? parsed.value : null;
 | 
					    return parsed.isSuccess ? parsed.value : null;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					// 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 <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:flutter/widgets.dart';
 | 
				
			||||||
 | 
					import 'package:wyatt_i18n/wyatt_i18n.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// {@template locale_parser}
 | 
				
			||||||
 | 
					/// A parser that parses a [String] into a [Locale].
 | 
				
			||||||
 | 
					/// Example:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// final parser = LocaleParser();
 | 
				
			||||||
 | 
					/// final locale = parser.parse('en-US');
 | 
				
			||||||
 | 
					/// // locale.languageCode == 'en'
 | 
				
			||||||
 | 
					/// // locale.countryCode == 'US'
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					/// {@endtemplate}
 | 
				
			||||||
 | 
					class LocaleParser extends Parser<String, Locale> {
 | 
				
			||||||
 | 
					  /// {@macro locale_parser}
 | 
				
			||||||
 | 
					  const LocaleParser() : super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Locale parse(String input) {
 | 
				
			||||||
 | 
					    final languageParts = RegExp('([a-z]*)[_-]?([a-z|A-Z]*)').firstMatch(input);
 | 
				
			||||||
 | 
					    final languageCode = languageParts?.group(1);
 | 
				
			||||||
 | 
					    final countryCode = languageParts?.group(2);
 | 
				
			||||||
 | 
					    if (languageCode == null) {
 | 
				
			||||||
 | 
					      throw ParserException("Can't parse locale tag '$input'", null);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Locale(languageCode, countryCode);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Serializes the given [locale] into a [String].
 | 
				
			||||||
 | 
					  String serialize(Locale locale) => locale.countryCode == null
 | 
				
			||||||
 | 
					      ? locale.languageCode
 | 
				
			||||||
 | 
					      : '${locale.languageCode}_${locale.countryCode}';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -14,9 +14,9 @@
 | 
				
			|||||||
// You should have received a copy of the GNU General Public License
 | 
					// You should have received a copy of the GNU General Public License
 | 
				
			||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
					// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export 'arb_parser.dart';
 | 
					 | 
				
			||||||
export 'i18n_file_parser.dart';
 | 
					 | 
				
			||||||
export 'icu_parser.dart';
 | 
					 | 
				
			||||||
export 'json_parser.dart';
 | 
					 | 
				
			||||||
export 'parser.dart';
 | 
					export 'parser.dart';
 | 
				
			||||||
export 'yaml_parser.dart';
 | 
					export 'parsers/arb_parser.dart';
 | 
				
			||||||
 | 
					export 'parsers/i18n_file_parser.dart';
 | 
				
			||||||
 | 
					export 'parsers/icu_parser.dart';
 | 
				
			||||||
 | 
					export 'parsers/json_parser.dart';
 | 
				
			||||||
 | 
					export 'parsers/yaml_parser.dart';
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,6 @@
 | 
				
			|||||||
// You should have received a copy of the GNU General Public License
 | 
					// You should have received a copy of the GNU General Public License
 | 
				
			||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
					// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export 'data_sources/assets_file_data_source_impl.dart';
 | 
					export '../domain/entities/i18n.dart';
 | 
				
			||||||
export 'data_sources/network_data_source_impl.dart';
 | 
					export 'data_sources/assets_i18n_data_source_impl.dart';
 | 
				
			||||||
export 'repositories/i18n_repository_impl.dart';
 | 
					export 'data_sources/network_i18n_data_source_impl.dart';
 | 
				
			||||||
 | 
				
			|||||||
@ -15,31 +15,61 @@
 | 
				
			|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
					// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:flutter/services.dart' show AssetBundle, rootBundle;
 | 
					import 'package:flutter/services.dart' show AssetBundle, rootBundle;
 | 
				
			||||||
import 'package:wyatt_i18n/src/core/enums/format.dart';
 | 
					import 'package:flutter/widgets.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/core/utils/assets_utils.dart';
 | 
				
			||||||
import 'package:wyatt_i18n/src/domain/data_sources/i18n_data_source.dart';
 | 
					import 'package:wyatt_i18n/src/core/utils/intl_utils.dart';
 | 
				
			||||||
 | 
					import 'package:wyatt_i18n/src/core/utils/parsers/locale_parser.dart';
 | 
				
			||||||
 | 
					import 'package:wyatt_i18n/wyatt_i18n.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// {@template assets_file_data_source_impl}
 | 
					/// {@template assets_i18n_data_source_impl}
 | 
				
			||||||
/// Implementation of [I18nDataSource] that loads i18n files from the assets.
 | 
					/// Implementation of [I18nDataSource] that loads i18n files from the assets.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// The [basePath] is the folder where the i18n files are located.
 | 
					/// The [basePath] is the folder where the i18n files are located.
 | 
				
			||||||
/// The [baseName] is the name of the i18n files without the extension.
 | 
					/// The [baseName] is the name of the i18n files without the extension.
 | 
				
			||||||
/// The [format] is the format of the i18n files.
 | 
					/// The [format] is the format of the i18n files.
 | 
				
			||||||
 | 
					/// The [defaultLocale] is the default locale to use when the locale is not
 | 
				
			||||||
 | 
					/// specified.
 | 
				
			||||||
 | 
					/// The [separator] is the separator to use when the locale is specified.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// For example, if the i18n files are located in the `assets/l10n/` and are
 | 
					/// For example, if the i18n files are located in the `assets/l10n/` and are
 | 
				
			||||||
/// named `i18n.en.arb`, `i18n.fr.arb`, etc., then the [basePath]
 | 
					/// named `i18n.en.arb`, `i18n.fr.arb`, etc., then the [basePath]
 | 
				
			||||||
/// is `l10n` and the [baseName] is `i18n` and the [format] is [Format.arb].
 | 
					/// is `l10n` and the [baseName] is `i18n` and the [format] is [Format.arb].
 | 
				
			||||||
/// {@endtemplate}
 | 
					/// {@endtemplate}
 | 
				
			||||||
class AssetsFileDataSourceImpl extends I18nDataSource {
 | 
					class AssetsI18nDataSourceImpl extends I18nDataSource {
 | 
				
			||||||
  /// {@macro assets_file_data_source_impl}
 | 
					  /// {@macro assets_i18n_data_source_impl}
 | 
				
			||||||
  AssetsFileDataSourceImpl({
 | 
					  AssetsI18nDataSourceImpl({
 | 
				
			||||||
    this.basePath = '',
 | 
					    this.basePath = '',
 | 
				
			||||||
    this.baseName = 'i18n',
 | 
					    this.baseName = 'i18n',
 | 
				
			||||||
    super.format = Format.arb,
 | 
					    super.format = Format.arb,
 | 
				
			||||||
    super.defaultLocale = 'en',
 | 
					    super.defaultLocale = const Locale('en'),
 | 
				
			||||||
 | 
					    super.separator = '.',
 | 
				
			||||||
 | 
					    super.localeTransformer,
 | 
				
			||||||
  }) : super();
 | 
					  }) : super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Creates a new instance of [AssetsI18nDataSourceImpl] with the system
 | 
				
			||||||
 | 
					  /// locale as the default locale.
 | 
				
			||||||
 | 
					  /// So, the [defaultLocale] is the system locale and you don't need to
 | 
				
			||||||
 | 
					  /// specify it when you load the i18n file.
 | 
				
			||||||
 | 
					  // ignore: long-parameter-list
 | 
				
			||||||
 | 
					  static Future<AssetsI18nDataSourceImpl> withSystemLocale({
 | 
				
			||||||
 | 
					    String basePath = '',
 | 
				
			||||||
 | 
					    String baseName = 'i18n',
 | 
				
			||||||
 | 
					    Format format = Format.arb,
 | 
				
			||||||
 | 
					    String separator = '.',
 | 
				
			||||||
 | 
					    LocaleTransformer? localeTransformer,
 | 
				
			||||||
 | 
					  }) async {
 | 
				
			||||||
 | 
					    final locale = await IntlUtils.getSystemLocale();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return AssetsI18nDataSourceImpl(
 | 
				
			||||||
 | 
					      basePath: basePath,
 | 
				
			||||||
 | 
					      baseName: baseName,
 | 
				
			||||||
 | 
					      format: format,
 | 
				
			||||||
 | 
					      defaultLocale: const LocaleParser().parse(locale),
 | 
				
			||||||
 | 
					      separator: separator,
 | 
				
			||||||
 | 
					      localeTransformer: localeTransformer,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// The folder where the i18n files are located.
 | 
					  /// The folder where the i18n files are located.
 | 
				
			||||||
  ///
 | 
					  ///
 | 
				
			||||||
  /// Note: The path is relative to the `assets` folder. So, `assets/l10n/`
 | 
					  /// Note: The path is relative to the `assets` folder. So, `assets/l10n/`
 | 
				
			||||||
@ -53,15 +83,20 @@ class AssetsFileDataSourceImpl extends I18nDataSource {
 | 
				
			|||||||
  final AssetBundle assetBundle = rootBundle;
 | 
					  final AssetBundle assetBundle = rootBundle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Tries to load the i18n file from the given [locale].
 | 
					  /// Tries to load the i18n file from the given [locale].
 | 
				
			||||||
  Future<String> _tryLoad(String? locale) async {
 | 
					  Future<I18n> _tryLoad(Locale? locale) async {
 | 
				
			||||||
    String? content;
 | 
					    String? content;
 | 
				
			||||||
    final ext = format.name;
 | 
					    final ext = format.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final path = AssetsUtils.cleanPath(
 | 
					    final defaultLocaleString = localeTransformer?.call(defaultLocale) ??
 | 
				
			||||||
      locale == null
 | 
					        const LocaleParser().serialize(defaultLocale);
 | 
				
			||||||
          ? '$basePath/$baseName.$defaultLocale.$ext'
 | 
					
 | 
				
			||||||
          : '$basePath/$baseName.$locale.$ext',
 | 
					    final localeString = locale == null
 | 
				
			||||||
    );
 | 
					        ? defaultLocaleString
 | 
				
			||||||
 | 
					        : localeTransformer?.call(locale) ??
 | 
				
			||||||
 | 
					            const LocaleParser().serialize(locale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final path =
 | 
				
			||||||
 | 
					        AssetsUtils.cleanPath('$basePath/$baseName.$localeString.$ext');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      if (await AssetsUtils.assetExists(path)) {
 | 
					      if (await AssetsUtils.assetExists(path)) {
 | 
				
			||||||
@ -76,7 +111,7 @@ class AssetsFileDataSourceImpl extends I18nDataSource {
 | 
				
			|||||||
    if (content == null && locale != null) {
 | 
					    if (content == null && locale != null) {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        final fallbackPath = AssetsUtils.cleanPath(
 | 
					        final fallbackPath = AssetsUtils.cleanPath(
 | 
				
			||||||
          '$basePath/$baseName.$defaultLocale.$ext',
 | 
					          '$basePath/$baseName.$defaultLocaleString.$ext',
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        content = await assetBundle.loadString(fallbackPath);
 | 
					        content = await assetBundle.loadString(fallbackPath);
 | 
				
			||||||
      } catch (_) {
 | 
					      } catch (_) {
 | 
				
			||||||
@ -90,12 +125,19 @@ class AssetsFileDataSourceImpl extends I18nDataSource {
 | 
				
			|||||||
      throw SourceNotFoundException(path, format: format);
 | 
					      throw SourceNotFoundException(path, format: format);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return content;
 | 
					    /// Parse the i18n file.
 | 
				
			||||||
 | 
					    final parsedData = format.parser.parse(content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return I18n(
 | 
				
			||||||
 | 
					      unparsedData: content,
 | 
				
			||||||
 | 
					      data: parsedData,
 | 
				
			||||||
 | 
					      locale: locale,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Tries to load the i18n file from a given [uri].
 | 
					  /// Tries to load the i18n file from a given [uri].
 | 
				
			||||||
  /// This method is used when the [uri] is not null.
 | 
					  /// This method is used when the [uri] is not null.
 | 
				
			||||||
  Future<String> _tryLoadUri(Uri uri) async {
 | 
					  Future<I18n> _tryLoadUri(Uri uri) async {
 | 
				
			||||||
    String? content;
 | 
					    String? content;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      content = await assetBundle.loadString(uri.toString());
 | 
					      content = await assetBundle.loadString(uri.toString());
 | 
				
			||||||
@ -103,7 +145,13 @@ class AssetsFileDataSourceImpl extends I18nDataSource {
 | 
				
			|||||||
      throw SourceNotFoundException(uri.toString(), format: format);
 | 
					      throw SourceNotFoundException(uri.toString(), format: format);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return content;
 | 
					    /// Parse the i18n file.
 | 
				
			||||||
 | 
					    final parsedData = format.parser.parse(content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return I18n(
 | 
				
			||||||
 | 
					      unparsedData: content,
 | 
				
			||||||
 | 
					      data: parsedData,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Loads the i18n file from Assets folder.
 | 
					  /// Loads the i18n file from Assets folder.
 | 
				
			||||||
@ -111,7 +159,8 @@ class AssetsFileDataSourceImpl extends I18nDataSource {
 | 
				
			|||||||
  /// The i18n file must be in [basePath], named [baseName] +
 | 
					  /// The i18n file must be in [basePath], named [baseName] +
 | 
				
			||||||
  /// `.<locale>.<extension>` and must be specified in the `pubspec.yaml` file.
 | 
					  /// `.<locale>.<extension>` and must be specified in the `pubspec.yaml` file.
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Future<String> load({required String? locale}) async => _tryLoad(locale);
 | 
					  Future<I18n> load({Locale? locale}) async =>
 | 
				
			||||||
 | 
					      super.currentI18nFile = await _tryLoad(locale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Loads the i18n file from Assets folder.
 | 
					  /// Loads the i18n file from Assets folder.
 | 
				
			||||||
  ///
 | 
					  ///
 | 
				
			||||||
@ -119,5 +168,6 @@ class AssetsFileDataSourceImpl extends I18nDataSource {
 | 
				
			|||||||
  /// from the given [uri]. In this case, the [basePath] and the
 | 
					  /// from the given [uri]. In this case, the [basePath] and the
 | 
				
			||||||
  /// [baseName] are ignored. And there is no fallback.
 | 
					  /// [baseName] are ignored. And there is no fallback.
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Future<String> loadFrom(Uri uri) async => _tryLoadUri(uri);
 | 
					  Future<I18n> loadFrom(Uri uri) async =>
 | 
				
			||||||
 | 
					      super.currentI18nFile = await _tryLoadUri(uri);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -15,19 +15,26 @@
 | 
				
			|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
					// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/widgets.dart';
 | 
				
			||||||
import 'package:http/http.dart' as http;
 | 
					import 'package:http/http.dart' as http;
 | 
				
			||||||
import 'package:wyatt_i18n/src/core/enums/format.dart';
 | 
					import 'package:wyatt_i18n/src/core/enums/format.dart';
 | 
				
			||||||
import 'package:wyatt_i18n/src/core/exceptions/exceptions.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/core/utils/assets_utils.dart';
 | 
				
			||||||
 | 
					import 'package:wyatt_i18n/src/core/utils/intl_utils.dart';
 | 
				
			||||||
 | 
					import 'package:wyatt_i18n/src/core/utils/parsers/locale_parser.dart';
 | 
				
			||||||
import 'package:wyatt_i18n/src/domain/data_sources/i18n_data_source.dart';
 | 
					import 'package:wyatt_i18n/src/domain/data_sources/i18n_data_source.dart';
 | 
				
			||||||
 | 
					import 'package:wyatt_i18n/src/domain/entities/i18n.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// {@template network_data_source_impl}
 | 
					/// {@template network_i18n_data_source_impl}
 | 
				
			||||||
/// Implementation of [I18nDataSource] that loads i18n files from the network.
 | 
					/// Implementation of [I18nDataSource] that loads i18n files from the network.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// The [baseUri] is the base uri where the i18n files are located.
 | 
					/// 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 [baseName] is the name of the i18n files without the extension.
 | 
				
			||||||
/// The [fallbackAssetPath] is the path to the fallback i18n files.
 | 
					/// The [fallbackAssetPath] is the path to the fallback i18n files.
 | 
				
			||||||
/// The [format] is the format of the i18n files.
 | 
					/// The [format] is the format of the i18n files.
 | 
				
			||||||
 | 
					/// The [defaultLocale] is the default locale to use when the locale is not
 | 
				
			||||||
 | 
					/// specified.
 | 
				
			||||||
 | 
					/// The [separator] is the separator to use when the locale is specified.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// For example, if the i18n files are located at `https://example.com/i18n/`
 | 
					/// 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
 | 
					/// and are named `i18n.en.arb`, `i18n.fr.arb`, etc., then the [baseUri] is
 | 
				
			||||||
@ -42,16 +49,42 @@ import 'package:wyatt_i18n/src/domain/data_sources/i18n_data_source.dart';
 | 
				
			|||||||
/// and are named `i18n.arb`, `i18n.en.arb`, `i18n.fr.arb`, etc., then the
 | 
					/// and are named `i18n.arb`, `i18n.en.arb`, `i18n.fr.arb`, etc., then the
 | 
				
			||||||
/// [fallbackAssetPath] is `l10n`.
 | 
					/// [fallbackAssetPath] is `l10n`.
 | 
				
			||||||
/// {@endtemplate}
 | 
					/// {@endtemplate}
 | 
				
			||||||
class NetworkDataSourceImpl extends I18nDataSource {
 | 
					class NetworkI18nDataSourceImpl extends I18nDataSource {
 | 
				
			||||||
  /// {@macro network_data_source_impl}
 | 
					  /// {@macro network_i18n_data_source_impl}
 | 
				
			||||||
  const NetworkDataSourceImpl({
 | 
					  NetworkI18nDataSourceImpl({
 | 
				
			||||||
    required this.baseUri,
 | 
					    required this.baseUri,
 | 
				
			||||||
    this.baseName = 'i18n',
 | 
					    this.baseName = 'i18n',
 | 
				
			||||||
    this.fallbackAssetPath = '',
 | 
					    this.fallbackAssetPath = '',
 | 
				
			||||||
    super.format = Format.arb,
 | 
					    super.format = Format.arb,
 | 
				
			||||||
    super.defaultLocale = 'en',
 | 
					    super.defaultLocale = const Locale('en'),
 | 
				
			||||||
 | 
					    super.separator = '.',
 | 
				
			||||||
 | 
					    super.localeTransformer,
 | 
				
			||||||
  }) : super();
 | 
					  }) : super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Creates a new instance of [NetworkI18nDataSourceImpl] with the system
 | 
				
			||||||
 | 
					  /// locale as the default locale.
 | 
				
			||||||
 | 
					  /// So, the [defaultLocale] is the system locale and you don't need to
 | 
				
			||||||
 | 
					  /// specify it when you load the i18n file.
 | 
				
			||||||
 | 
					  // ignore: long-parameter-list
 | 
				
			||||||
 | 
					  static Future<NetworkI18nDataSourceImpl> withSystemLocale({
 | 
				
			||||||
 | 
					    required Uri baseUri,
 | 
				
			||||||
 | 
					    String baseName = 'i18n',
 | 
				
			||||||
 | 
					    Format format = Format.arb,
 | 
				
			||||||
 | 
					    String separator = '.',
 | 
				
			||||||
 | 
					    LocaleTransformer? localeTransformer,
 | 
				
			||||||
 | 
					  }) async {
 | 
				
			||||||
 | 
					    final locale = await IntlUtils.getSystemLocale();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NetworkI18nDataSourceImpl(
 | 
				
			||||||
 | 
					      baseUri: baseUri,
 | 
				
			||||||
 | 
					      baseName: baseName,
 | 
				
			||||||
 | 
					      format: format,
 | 
				
			||||||
 | 
					      defaultLocale: const LocaleParser().parse(locale),
 | 
				
			||||||
 | 
					      separator: separator,
 | 
				
			||||||
 | 
					      localeTransformer: localeTransformer,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// The base uri where the i18n files are located.
 | 
					  /// The base uri where the i18n files are located.
 | 
				
			||||||
  final Uri baseUri;
 | 
					  final Uri baseUri;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -68,18 +101,26 @@ class NetworkDataSourceImpl extends I18nDataSource {
 | 
				
			|||||||
  final String fallbackAssetPath;
 | 
					  final String fallbackAssetPath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Tries to load the i18n file from the given [locale].
 | 
					  /// Tries to load the i18n file from the given [locale].
 | 
				
			||||||
  Future<String> _tryLoad(String? locale, {Uri? overrideUri}) async {
 | 
					  Future<I18n> _tryLoad(Locale? locale, {Uri? overrideUri}) async {
 | 
				
			||||||
    String? content;
 | 
					    String? content;
 | 
				
			||||||
    final ext = format.name;
 | 
					    final ext = format.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final defaultLocaleString = localeTransformer?.call(defaultLocale) ??
 | 
				
			||||||
 | 
					        const LocaleParser().serialize(defaultLocale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final localeString = locale == null
 | 
				
			||||||
 | 
					        ? defaultLocaleString
 | 
				
			||||||
 | 
					        : localeTransformer?.call(locale) ??
 | 
				
			||||||
 | 
					            const LocaleParser().serialize(locale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// If the locale is null, then we try to load the default i18n file from
 | 
					    /// If the locale is null, then we try to load the default i18n file from
 | 
				
			||||||
    /// the fallback asset path.
 | 
					    /// the fallback asset path.
 | 
				
			||||||
    /// Otherwise, we try to load the i18n file for the given locale from the
 | 
					    /// Otherwise, we try to load the i18n file for the given locale from the
 | 
				
			||||||
    /// base uri.
 | 
					    /// base uri.
 | 
				
			||||||
    final path = AssetsUtils.cleanPath(
 | 
					    final path = AssetsUtils.cleanPath(
 | 
				
			||||||
      locale == null
 | 
					      locale == null
 | 
				
			||||||
          ? '$fallbackAssetPath/$baseName.$defaultLocale.$ext'
 | 
					          ? '$fallbackAssetPath/$baseName.$defaultLocaleString.$ext'
 | 
				
			||||||
          : overrideUri?.toString() ?? '$baseUri/$baseName.$locale.$ext',
 | 
					          : overrideUri?.toString() ?? '$baseUri/$baseName.$localeString.$ext',
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (locale == null) {
 | 
					    if (locale == null) {
 | 
				
			||||||
@ -108,7 +149,7 @@ class NetworkDataSourceImpl extends I18nDataSource {
 | 
				
			|||||||
    if (content == null && locale != null) {
 | 
					    if (content == null && locale != null) {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        final fallbackPath = AssetsUtils.cleanPath(
 | 
					        final fallbackPath = AssetsUtils.cleanPath(
 | 
				
			||||||
          '$fallbackAssetPath/$baseName.$defaultLocale.$ext',
 | 
					          '$fallbackAssetPath/$baseName.$defaultLocaleString.$ext',
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        content = await rootBundle.loadString(fallbackPath);
 | 
					        content = await rootBundle.loadString(fallbackPath);
 | 
				
			||||||
      } catch (_) {
 | 
					      } catch (_) {
 | 
				
			||||||
@ -122,7 +163,14 @@ class NetworkDataSourceImpl extends I18nDataSource {
 | 
				
			|||||||
      throw SourceNotFoundException(path, format: format);
 | 
					      throw SourceNotFoundException(path, format: format);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return content;
 | 
					    /// Parse the i18n file.
 | 
				
			||||||
 | 
					    final parsedData = format.parser.parse(content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return I18n(
 | 
				
			||||||
 | 
					      unparsedData: content,
 | 
				
			||||||
 | 
					      data: parsedData,
 | 
				
			||||||
 | 
					      locale: locale,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Loads the i18n file for the given [locale].
 | 
					  /// Loads the i18n file for the given [locale].
 | 
				
			||||||
@ -130,12 +178,14 @@ class NetworkDataSourceImpl extends I18nDataSource {
 | 
				
			|||||||
  /// If the [locale] is null, then the default i18n file is loaded from the
 | 
					  /// If the [locale] is null, then the default i18n file is loaded from the
 | 
				
			||||||
  /// fallback asset path.
 | 
					  /// fallback asset path.
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Future<String> load({required String? locale}) async => _tryLoad(locale);
 | 
					  Future<I18n> load({Locale? locale}) async =>
 | 
				
			||||||
 | 
					      super.currentI18nFile = await _tryLoad(locale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Loads the i18n file from the given [uri].
 | 
					  /// Loads the i18n file from the given [uri].
 | 
				
			||||||
  ///
 | 
					  ///
 | 
				
			||||||
  /// If the fetch fails, then the fallback i18n files are loaded from the
 | 
					  /// If the fetch fails, then the fallback i18n files are loaded from the
 | 
				
			||||||
  /// [fallbackAssetPath] in the root bundle.
 | 
					  /// [fallbackAssetPath] in the root bundle.
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Future<String> loadFrom(Uri uri) async => _tryLoad(null, overrideUri: uri);
 | 
					  Future<I18n> loadFrom(Uri uri) async =>
 | 
				
			||||||
 | 
					      super.currentI18nFile = await _tryLoad(null, overrideUri: uri);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,157 +0,0 @@
 | 
				
			|||||||
// 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 <https://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:wyatt_architecture/wyatt_architecture.dart';
 | 
					 | 
				
			||||||
import 'package:wyatt_i18n/wyatt_i18n.dart';
 | 
					 | 
				
			||||||
import 'package:wyatt_type_utils/wyatt_type_utils.dart' hide Option;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// {@template i18n_repository_impl}
 | 
					 | 
				
			||||||
/// The default implementation of [I18nRepository].
 | 
					 | 
				
			||||||
/// {@endtemplate}
 | 
					 | 
				
			||||||
class I18nRepositoryImpl extends I18nRepository {
 | 
					 | 
				
			||||||
  /// {@macro i18n_repository_impl}
 | 
					 | 
				
			||||||
  I18nRepositoryImpl({
 | 
					 | 
				
			||||||
    required this.dataSource,
 | 
					 | 
				
			||||||
  }) : super();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// The data source used to load the i18n file.
 | 
					 | 
				
			||||||
  final I18nDataSource dataSource;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// The current i18n instance.
 | 
					 | 
				
			||||||
  I18nFile _i18n = const I18nFile.empty();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  I18nFile get i18n => _i18n;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<I18nFile> _parse(
 | 
					 | 
				
			||||||
    String content,
 | 
					 | 
				
			||||||
    Parser<String, Map<String, dynamic>> parser, {
 | 
					 | 
				
			||||||
    String? locale,
 | 
					 | 
				
			||||||
    bool strict = false,
 | 
					 | 
				
			||||||
  }) async {
 | 
					 | 
				
			||||||
    final parsed = parser.parse(content);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    String parsedLocale;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Checks if the locale is present in the parsed data.
 | 
					 | 
				
			||||||
    /// If not, throws an exception.
 | 
					 | 
				
			||||||
    /// If yes, sets the locale to the parsed locale.
 | 
					 | 
				
			||||||
    if (parsed.containsKey('@@locale')) {
 | 
					 | 
				
			||||||
      if (strict) {
 | 
					 | 
				
			||||||
        /// Checks if the parsed locale is a string.
 | 
					 | 
				
			||||||
        if (parsed['@@locale'] is! String) {
 | 
					 | 
				
			||||||
          throw NoLocaleException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// Checks if the parsed locale is the same as the given locale.
 | 
					 | 
				
			||||||
        if (locale != null && parsed['@@locale'] as String != locale) {
 | 
					 | 
				
			||||||
          throw InvalidLocaleException(locale, parsed['@@locale'] as String);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      parsedLocale = parsed['@@locale'] as String;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      if (strict) {
 | 
					 | 
				
			||||||
        /// Throws an exception if the locale is not present in the parsed data.
 | 
					 | 
				
			||||||
        throw NoLocaleException();
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        /// Sets the locale to the given locale.
 | 
					 | 
				
			||||||
        /// If the given locale is null, sets the locale to 'null'.
 | 
					 | 
				
			||||||
        /// This is done to prevent the locale from being null.
 | 
					 | 
				
			||||||
        /// It should never be null.
 | 
					 | 
				
			||||||
        parsedLocale = locale ?? 'null';
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return I18nFile(
 | 
					 | 
				
			||||||
      locale: parsedLocale,
 | 
					 | 
				
			||||||
      unparsedData: content,
 | 
					 | 
				
			||||||
      data: parsed,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  FutureOrResult<I18nFile> load({
 | 
					 | 
				
			||||||
    required String? locale,
 | 
					 | 
				
			||||||
    bool strict = false,
 | 
					 | 
				
			||||||
    Parser<String, Map<String, dynamic>>? parser,
 | 
					 | 
				
			||||||
  }) async =>
 | 
					 | 
				
			||||||
      await Result.tryCatchAsync<I18nFile, AppException, AppException>(
 | 
					 | 
				
			||||||
        () async {
 | 
					 | 
				
			||||||
          final content = await dataSource.load(locale: locale);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          return _i18n = await _parse(
 | 
					 | 
				
			||||||
            content,
 | 
					 | 
				
			||||||
            parser ?? dataSource.format.parser,
 | 
					 | 
				
			||||||
            locale: locale,
 | 
					 | 
				
			||||||
            strict: strict,
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        (error) => error,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  FutureOrResult<I18nFile> loadFrom(
 | 
					 | 
				
			||||||
    Uri uri, {
 | 
					 | 
				
			||||||
    Parser<String, Map<String, dynamic>>? parser,
 | 
					 | 
				
			||||||
  }) async =>
 | 
					 | 
				
			||||||
      await Result.tryCatchAsync<I18nFile, AppException, AppException>(
 | 
					 | 
				
			||||||
        () async {
 | 
					 | 
				
			||||||
          final content = await dataSource.loadFrom(uri);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          /// Strict is always true when loading from a uri. Because
 | 
					 | 
				
			||||||
          /// the locale is not given and can't be inferred.
 | 
					 | 
				
			||||||
          return _i18n = await _parse(
 | 
					 | 
				
			||||||
            content,
 | 
					 | 
				
			||||||
            parser ?? dataSource.format.parser,
 | 
					 | 
				
			||||||
            strict: true,
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        (error) => error,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  Result<String, AppException> get(
 | 
					 | 
				
			||||||
    String key, [
 | 
					 | 
				
			||||||
    Map<String, dynamic> arguments = const {},
 | 
					 | 
				
			||||||
  ]) {
 | 
					 | 
				
			||||||
    final I18nFileParser parser =
 | 
					 | 
				
			||||||
        I18nFileParser(i18n: _i18n, arguments: arguments);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (_i18n.containsKey(key)) {
 | 
					 | 
				
			||||||
      return Result.tryCatch<String, AppException, AppException>(
 | 
					 | 
				
			||||||
        () => parser.parse(_i18n[key] as String, key: key),
 | 
					 | 
				
			||||||
        (error) => error,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      throw KeyNotFoundException(key, arguments);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  Result<I18nFile, AppException> getI18n() =>
 | 
					 | 
				
			||||||
      Result.conditional(!_i18n.isEmpty, _i18n, NotLoadedException());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  Result<String, AppException> getLocale() =>
 | 
					 | 
				
			||||||
      Result.conditional(!_i18n.isEmpty, _i18n.locale, NotLoadedException());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  Result<void, AppException> setI18n(I18nFile i18n) {
 | 
					 | 
				
			||||||
    _i18n = i18n;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return const Ok(null);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -14,8 +14,11 @@
 | 
				
			|||||||
// You should have received a copy of the GNU General Public License
 | 
					// You should have received a copy of the GNU General Public License
 | 
				
			||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
					// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:flutter/widgets.dart';
 | 
				
			||||||
import 'package:wyatt_architecture/wyatt_architecture.dart';
 | 
					import 'package:wyatt_architecture/wyatt_architecture.dart';
 | 
				
			||||||
import 'package:wyatt_i18n/src/core/enums/format.dart';
 | 
					import 'package:wyatt_i18n/wyatt_i18n.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef LocaleTransformer = String Function(Locale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// {@template i18n_data_source}
 | 
					/// {@template i18n_data_source}
 | 
				
			||||||
/// Base class for i18n data sources.
 | 
					/// Base class for i18n data sources.
 | 
				
			||||||
@ -26,24 +29,62 @@ import 'package:wyatt_i18n/src/core/enums/format.dart';
 | 
				
			|||||||
/// {@endtemplate}
 | 
					/// {@endtemplate}
 | 
				
			||||||
abstract class I18nDataSource extends BaseDataSource {
 | 
					abstract class I18nDataSource extends BaseDataSource {
 | 
				
			||||||
  /// {@macro i18n_data_source}
 | 
					  /// {@macro i18n_data_source}
 | 
				
			||||||
  const I18nDataSource({
 | 
					  I18nDataSource({
 | 
				
			||||||
    this.format = Format.arb,
 | 
					    this.format = Format.arb,
 | 
				
			||||||
    this.defaultLocale = 'en',
 | 
					    this.defaultLocale = const Locale('en'),
 | 
				
			||||||
 | 
					    this.separator = '.',
 | 
				
			||||||
 | 
					    this.localeTransformer,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// The format of the i18n file.
 | 
					  /// The format of the i18n file.
 | 
				
			||||||
  final Format format;
 | 
					  final Format format;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// The default locale.
 | 
					  /// The default locale.
 | 
				
			||||||
  final String defaultLocale;
 | 
					  /// This is used when the locale is `null` in the [load] method.
 | 
				
			||||||
 | 
					  Locale defaultLocale;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The separator used in file name.
 | 
				
			||||||
 | 
					  /// This is used to separate the baseName and the locale.
 | 
				
			||||||
 | 
					  ///
 | 
				
			||||||
 | 
					  /// For example, if the baseName is `i18n` and the locale is `en`, if the
 | 
				
			||||||
 | 
					  /// separator is `_`, the path will be `i18n_en`.
 | 
				
			||||||
 | 
					  final String separator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The list of parsers used to parse the i18n file.
 | 
				
			||||||
 | 
					  /// This is used to parse the i18n file into a [Map].
 | 
				
			||||||
 | 
					  // final List<Parser<String, Map<String, dynamic>>> decoders;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The current i18n file loaded from the source.
 | 
				
			||||||
 | 
					  /// This is used to avoid loading the same file twice.
 | 
				
			||||||
 | 
					  I18n? currentI18nFile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Function used to transform the [Locale] into a string.
 | 
				
			||||||
 | 
					  /// This is used to get the file name from the [Locale].
 | 
				
			||||||
 | 
					  ///
 | 
				
			||||||
 | 
					  /// For example, if the [Locale] is `en_US`, the file name will be `en_us`.
 | 
				
			||||||
 | 
					  ///
 | 
				
			||||||
 | 
					  /// But maybe you want to use `en-us` instead. In this case, you can use
 | 
				
			||||||
 | 
					  /// ```dart
 | 
				
			||||||
 | 
					  /// localeTransformer: (locale) => locale.toString().replaceAll('_', '-'),
 | 
				
			||||||
 | 
					  /// ```
 | 
				
			||||||
 | 
					  /// to replace the `_` with `-`.
 | 
				
			||||||
 | 
					  ///
 | 
				
			||||||
 | 
					  /// If you want to use `en` instead of `en_US`, you can use
 | 
				
			||||||
 | 
					  /// ```dart
 | 
				
			||||||
 | 
					  /// localeTransformer: (locale) => locale.languageCode,
 | 
				
			||||||
 | 
					  /// ```
 | 
				
			||||||
 | 
					  ///
 | 
				
			||||||
 | 
					  /// If this is `null`, the default transformer will be used.
 | 
				
			||||||
 | 
					  /// The default transformer will use the LocaleParser serializer method.
 | 
				
			||||||
 | 
					  LocaleTransformer? localeTransformer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Loads the i18n file from the source.
 | 
					  /// Loads the i18n file from the source.
 | 
				
			||||||
  /// If [locale] is not `null`, it will load the file with the given [locale].
 | 
					  /// If [locale] is not `null`, it will load the file with the given [locale].
 | 
				
			||||||
  /// Otherwise, it will load the file from the default location.
 | 
					  /// Otherwise, it will load the file from the default location.
 | 
				
			||||||
  Future<String> load({required String? locale});
 | 
					  Future<I18n> load({Locale? locale});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Loads the i18n file from the source.
 | 
					  /// Loads the i18n file from the source.
 | 
				
			||||||
  ///
 | 
					  ///
 | 
				
			||||||
  /// This method is used to load the i18n file from the given [Uri].
 | 
					  /// This method is used to load the i18n file from the given [Uri].
 | 
				
			||||||
  Future<String> loadFrom(Uri uri);
 | 
					  Future<I18n> loadFrom(Uri uri);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -16,5 +16,3 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export 'data_sources/i18n_data_source.dart';
 | 
					export 'data_sources/i18n_data_source.dart';
 | 
				
			||||||
export 'entities/i18n.dart';
 | 
					export 'entities/i18n.dart';
 | 
				
			||||||
export 'entities/i18n_file.dart';
 | 
					 | 
				
			||||||
export 'repositories/i18n_repository.dart';
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -14,47 +14,104 @@
 | 
				
			|||||||
// You should have received a copy of the GNU General Public License
 | 
					// You should have received a copy of the GNU General Public License
 | 
				
			||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
					// along with this program. If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:equatable/equatable.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/widgets.dart';
 | 
				
			||||||
import 'package:wyatt_architecture/wyatt_architecture.dart';
 | 
					import 'package:wyatt_architecture/wyatt_architecture.dart';
 | 
				
			||||||
import 'package:wyatt_i18n/wyatt_i18n.dart';
 | 
					import 'package:wyatt_i18n/src/core/exceptions/exceptions.dart';
 | 
				
			||||||
 | 
					import 'package:wyatt_i18n/src/core/utils/parsers/i18n_file_parser.dart';
 | 
				
			||||||
 | 
					import 'package:wyatt_i18n/src/core/utils/parsers/locale_parser.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// {@template i18n}
 | 
					/// {@template i18n_file}
 | 
				
			||||||
/// This class is used to store the translations of the application.
 | 
					/// Data structure for i18n files.
 | 
				
			||||||
/// This entity is used by the I18nDelegate and Flutter's Localizations
 | 
					 | 
				
			||||||
/// widget to provide the translations to the application.
 | 
					 | 
				
			||||||
/// {@endtemplate}
 | 
					/// {@endtemplate}
 | 
				
			||||||
class I18n extends Entity {
 | 
					class I18n extends Entity with EquatableMixin {
 | 
				
			||||||
  /// {@macro i18n}
 | 
					  /// {@macro i18n_file}
 | 
				
			||||||
  I18n({
 | 
					  const I18n({
 | 
				
			||||||
    required this.i18nRepository,
 | 
					    required this.unparsedData,
 | 
				
			||||||
 | 
					    required this.data,
 | 
				
			||||||
 | 
					    this.locale,
 | 
				
			||||||
  }) : super();
 | 
					  }) : super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final I18nRepository i18nRepository;
 | 
					  /// Creates an empty i18n file.
 | 
				
			||||||
 | 
					  const I18n.empty() : this(unparsedData: '', data: const {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Get the translation of the given [key].
 | 
					  /// The locale of the i18n file.
 | 
				
			||||||
  /// If the [key] is not found, the [key] itself is returned.
 | 
					  final Locale? locale;
 | 
				
			||||||
  /// If the [key] is found, the translation is returned.
 | 
					 | 
				
			||||||
  /// If [args] is not null, the translation is formatted with the
 | 
					 | 
				
			||||||
  /// given arguments.
 | 
					 | 
				
			||||||
  String get(String key, [Map<String, dynamic>? args]) {
 | 
					 | 
				
			||||||
    final result = i18nRepository.get(key, args ?? const <String, dynamic>{});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return result.fold(
 | 
					  /// The unparsed data of the i18n file.
 | 
				
			||||||
      (value) => value,
 | 
					  final String unparsedData;
 | 
				
			||||||
      (error) => key,
 | 
					
 | 
				
			||||||
    );
 | 
					  /// The data of the i18n file.
 | 
				
			||||||
 | 
					  final Map<String, dynamic> data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Gets the locale of the i18n file.
 | 
				
			||||||
 | 
					  /// If [strict] is true, it will throw an exception if the locale is not
 | 
				
			||||||
 | 
					  /// present in the i18n file.
 | 
				
			||||||
 | 
					  /// If [strict] is false, it will return the locale of the i18n file if it is
 | 
				
			||||||
 | 
					  /// present, otherwise it will return the locale passed to the constructor.
 | 
				
			||||||
 | 
					  Locale getLocale({bool strict = false}) {
 | 
				
			||||||
 | 
					    if (strict) {
 | 
				
			||||||
 | 
					      if (data.containsKey('@@locale')) {
 | 
				
			||||||
 | 
					        if (data['@@locale'] is! String) {
 | 
				
			||||||
 | 
					          throw NoLocaleException();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return const LocaleParser().parse(data['@@locale'] as String);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        throw NoLocaleException();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      if (data.containsKey('@@locale')) {
 | 
				
			||||||
 | 
					        if (data['@@locale'] is! String) {
 | 
				
			||||||
 | 
					          if (locale != null) {
 | 
				
			||||||
 | 
					            return locale!;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            throw NoLocaleException();
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return const LocaleParser().parse(data['@@locale'] as String);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        if (locale != null) {
 | 
				
			||||||
 | 
					          return locale!;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          throw NoLocaleException();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Get the translation of the given [key].
 | 
					  /// Gets the value of a key.
 | 
				
			||||||
  ///
 | 
					  /// If the key is not present in the i18n file, it will throw a
 | 
				
			||||||
  /// Note: arguments are not supported.
 | 
					  /// [KeyNotFoundException].
 | 
				
			||||||
  String operator [](String key) => get(key);
 | 
					  /// If the key is present in the i18n file, it will return the value of the
 | 
				
			||||||
 | 
					  /// key parsed with ICU message format.
 | 
				
			||||||
 | 
					  String get(String key, [Map<String, dynamic>? args]) {
 | 
				
			||||||
 | 
					    final arguments = args ?? const <String, dynamic>{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// The parser used to parse the i18n file with ICU message format.
 | 
				
			||||||
 | 
					    final I18nFileParser parser =
 | 
				
			||||||
 | 
					        I18nFileParser(i18n: this, arguments: arguments);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (containsKey(key)) {
 | 
				
			||||||
 | 
					      return parser.parse(this[key] as String, key: key);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      throw KeyNotFoundException(key, arguments);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Get the translation of the given [key].
 | 
					  /// Get the translation of the given [key].
 | 
				
			||||||
  String call(String key, [Map<String, dynamic>? args]) => get(key, args);
 | 
					  String call(String key, [Map<String, dynamic>? args]) => get(key, args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Load the translations from the given [locale].
 | 
					  /// Checks if the i18n file contains a key.
 | 
				
			||||||
  /// If the [locale] is not found, the default locale is loaded.
 | 
					  bool containsKey(String key) => data.containsKey(key);
 | 
				
			||||||
  Future<void> load(String locale) async {
 | 
					
 | 
				
			||||||
    await i18nRepository.load(locale: locale);
 | 
					  /// Gets the value of a key.
 | 
				
			||||||
  }
 | 
					  dynamic operator [](String key) => data[key];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Checks if the i18n file is empty.
 | 
				
			||||||
 | 
					  bool get isEmpty => data.isEmpty && unparsedData.isEmpty && locale == null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  List<Object?> get props => [locale, unparsedData, data];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,54 +0,0 @@
 | 
				
			|||||||
// 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 <https://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:equatable/equatable.dart';
 | 
					 | 
				
			||||||
import 'package:wyatt_architecture/wyatt_architecture.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// {@template i18n_file}
 | 
					 | 
				
			||||||
/// Data structure for i18n files.
 | 
					 | 
				
			||||||
/// {@endtemplate}
 | 
					 | 
				
			||||||
class I18nFile extends Entity with EquatableMixin {
 | 
					 | 
				
			||||||
  /// {@macro i18n_file}
 | 
					 | 
				
			||||||
  const I18nFile({
 | 
					 | 
				
			||||||
    required this.locale,
 | 
					 | 
				
			||||||
    required this.unparsedData,
 | 
					 | 
				
			||||||
    required this.data,
 | 
					 | 
				
			||||||
  }) : super();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Creates an empty i18n file.
 | 
					 | 
				
			||||||
  const I18nFile.empty() : this(locale: '', unparsedData: '', data: const {});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// The locale of the i18n file.
 | 
					 | 
				
			||||||
  final String locale;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// The unparsed data of the i18n file.
 | 
					 | 
				
			||||||
  final String unparsedData;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// The data of the i18n file.
 | 
					 | 
				
			||||||
  final Map<String, dynamic> data;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Checks if the i18n file contains a key.
 | 
					 | 
				
			||||||
  bool containsKey(String key) => data.containsKey(key);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Gets the value of a key.
 | 
					 | 
				
			||||||
  dynamic operator [](String key) => data[key];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Checks if the i18n file is empty.
 | 
					 | 
				
			||||||
  bool get isEmpty => data.isEmpty && unparsedData.isEmpty && locale.isEmpty;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  List<Object?> get props => [locale, unparsedData, data];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,77 +0,0 @@
 | 
				
			|||||||
// 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 <https://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:wyatt_architecture/wyatt_architecture.dart';
 | 
					 | 
				
			||||||
import 'package:wyatt_i18n/src/core/utils/parser.dart';
 | 
					 | 
				
			||||||
import 'package:wyatt_i18n/src/domain/entities/i18n_file.dart';
 | 
					 | 
				
			||||||
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// {@template i18n_repository}
 | 
					 | 
				
			||||||
/// Base class for i18n repositories.
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// This class is used to manage i18n files.
 | 
					 | 
				
			||||||
/// {@endtemplate}
 | 
					 | 
				
			||||||
abstract class I18nRepository extends BaseRepository {
 | 
					 | 
				
			||||||
  /// {@macro i18n_repository}
 | 
					 | 
				
			||||||
  const I18nRepository() : super();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// The current i18n file.
 | 
					 | 
				
			||||||
  I18nFile get i18n;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Loads the i18n file from the source.
 | 
					 | 
				
			||||||
  /// If [strict] is `true`, it will throw an NoLocaleException if the
 | 
					 | 
				
			||||||
  /// `@@locale` key is not found in the i18n file, otherwise it will
 | 
					 | 
				
			||||||
  /// set the locale to the given [locale].
 | 
					 | 
				
			||||||
  /// If [parser] is not `null`, it will use the given parser to parse
 | 
					 | 
				
			||||||
  /// the i18n file. Otherwise, it will use the default parser for the format.
 | 
					 | 
				
			||||||
  FutureOrResult<I18nFile> load({
 | 
					 | 
				
			||||||
    required String? locale,
 | 
					 | 
				
			||||||
    bool strict = false,
 | 
					 | 
				
			||||||
    Parser<String, Map<String, dynamic>>? parser,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Loads the i18n file from the given [uri].
 | 
					 | 
				
			||||||
  /// If [parser] is not `null`, it will use the given parser to parse
 | 
					 | 
				
			||||||
  /// the i18n file. Otherwise, it will use the default parser for the format.
 | 
					 | 
				
			||||||
  FutureOrResult<I18nFile> loadFrom(
 | 
					 | 
				
			||||||
    Uri uri, {
 | 
					 | 
				
			||||||
    Parser<String, Map<String, dynamic>>? parser,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Gets the translation for the given [key].
 | 
					 | 
				
			||||||
  ///
 | 
					 | 
				
			||||||
  /// If [arguments] is not `null`, it will replace the placeholders in the
 | 
					 | 
				
			||||||
  /// translation with the given arguments.
 | 
					 | 
				
			||||||
  Result<String, AppException> get(
 | 
					 | 
				
			||||||
    String key, [
 | 
					 | 
				
			||||||
    Map<String, dynamic> arguments = const {},
 | 
					 | 
				
			||||||
  ]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Sets the current i18n instance.
 | 
					 | 
				
			||||||
  ///
 | 
					 | 
				
			||||||
  /// This method is used to set the current i18n instance.
 | 
					 | 
				
			||||||
  Result<void, AppException> setI18n(I18nFile i18n);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Gets the current i18n instance.
 | 
					 | 
				
			||||||
  ///
 | 
					 | 
				
			||||||
  /// This method is used to get the current i18n instance.
 | 
					 | 
				
			||||||
  Result<I18nFile, AppException> getI18n();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Gets the current locale.
 | 
					 | 
				
			||||||
  ///
 | 
					 | 
				
			||||||
  /// This method is used to get the current locale.
 | 
					 | 
				
			||||||
  Result<String, AppException> getLocale();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -18,21 +18,21 @@ import 'package:flutter/cupertino.dart';
 | 
				
			|||||||
import 'package:wyatt_i18n/wyatt_i18n.dart';
 | 
					import 'package:wyatt_i18n/wyatt_i18n.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// {@template i18n_delegate}
 | 
					/// {@template i18n_delegate}
 | 
				
			||||||
/// A [LocalizationsDelegate] that loads the [I18n] instance.
 | 
					/// A [LocalizationsDelegate] that loads the [I18n] instance from
 | 
				
			||||||
 | 
					/// the [I18nDataSource].
 | 
				
			||||||
/// {@endtemplate}
 | 
					/// {@endtemplate}
 | 
				
			||||||
class I18nDelegate extends LocalizationsDelegate<I18n> {
 | 
					class I18nDelegate extends LocalizationsDelegate<I18n> {
 | 
				
			||||||
  /// {@macro i18n_delegate}
 | 
					  /// {@macro i18n_delegate}
 | 
				
			||||||
  I18nDelegate({
 | 
					  I18nDelegate({
 | 
				
			||||||
    required this.repository,
 | 
					    required this.dataSource,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// The [I18nRepository] that will be used to load the i18n file.
 | 
					  final I18nDataSource dataSource;
 | 
				
			||||||
  final I18nRepository repository;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// The current locale.
 | 
					  /// The current locale.
 | 
				
			||||||
  Locale? currentLocale;
 | 
					  Locale? currentLocale;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Since we are using the [I18nRepository] to load the i18n file,
 | 
					  /// Since we are using the [I18nDataSource] to load the i18n file,
 | 
				
			||||||
  /// we don't need to check if the locale is supported.
 | 
					  /// we don't need to check if the locale is supported.
 | 
				
			||||||
  /// We can just return `true`.
 | 
					  /// We can just return `true`.
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@ -40,9 +40,10 @@ class I18nDelegate extends LocalizationsDelegate<I18n> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Future<I18n> load(Locale locale) async {
 | 
					  Future<I18n> load(Locale locale) async {
 | 
				
			||||||
    await repository.load(locale: locale.languageCode);
 | 
					    currentLocale = locale;
 | 
				
			||||||
 | 
					    final i18n = await dataSource.load(locale: locale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return I18n(i18nRepository: repository);
 | 
					    return i18n;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
 | 
				
			|||||||
@ -1,23 +0,0 @@
 | 
				
			|||||||
/// The i18n works as follows:
 | 
					 | 
				
			||||||
/// 1. The i18n file is loaded from a source.
 | 
					 | 
				
			||||||
///   - The i18n file is loaded from the assets folder.
 | 
					 | 
				
			||||||
///     - The i18n file can be in json, yaml or ARB format.
 | 
					 | 
				
			||||||
///     - The i18n file must contain a `@@locale` key with the locale code.
 | 
					 | 
				
			||||||
///     - The i18n file must be named `i18n.<locale>.<extension>`.
 | 
					 | 
				
			||||||
///   - The i18n file is loaded from a network source.
 | 
					 | 
				
			||||||
///     - The i18n file can be in json, yaml or ARB format.
 | 
					 | 
				
			||||||
///     - The i18n file must contain a `@@locale` key with the locale code.
 | 
					 | 
				
			||||||
///     - The assets folder must contain a `i18n.<locale>.<extension>` or
 | 
					 | 
				
			||||||
/// `i18n.<extension>` files used as a fallback.
 | 
					 | 
				
			||||||
/// 2. The i18n file is parsed and the locale is extracted.
 | 
					 | 
				
			||||||
/// 3. The i18n file is added to the i18n map.
 | 
					 | 
				
			||||||
/// 4. The i18n map is provided to the app.
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// The i18n map can be reloaded by calling the `reload` method.
 | 
					 | 
				
			||||||
/// This will reload the i18n file from the source and update the i18n map.
 | 
					 | 
				
			||||||
/// And update any widgets that are listening to the i18n map.
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// A top level BLoC is used to provide the i18n map to the app.
 | 
					 | 
				
			||||||
void main() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -87,13 +87,10 @@ class RichTextBuilder extends RichTextBuilderComponent
 | 
				
			|||||||
            );
 | 
					            );
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					    return Text.rich(
 | 
				
			||||||
    return SelectionArea(
 | 
					      TextSpan(children: [root.toInlineSpan(customParser)]),
 | 
				
			||||||
      child: Text.rich(
 | 
					      textHeightBehavior:
 | 
				
			||||||
        TextSpan(children: [root.toInlineSpan(customParser)]),
 | 
					          const TextHeightBehavior(applyHeightToLastDescent: false),
 | 
				
			||||||
        textHeightBehavior:
 | 
					 | 
				
			||||||
            const TextHeightBehavior(applyHeightToLastDescent: false),
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user