Skip to content

Commit

Permalink
add extra asset loaders (#654)
Browse files Browse the repository at this point in the history
* add extra asset loaders

* make some changes based on code review

* minor refactor
  • Loading branch information
hamed-rezaee authored Mar 1, 2024
1 parent 76484a9 commit 63a3974
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

### [3.0.5]

- add 'extraAssetLoaders' to add more assets loaders if it is needed, for example, if you want to add packages localizations to your project.

### [3.0.4]

- determine plural cases based on the actual language rules (#620)
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ class MyApp extends StatelessWidget {
| supportedLocales | true | | List of supported locales. |
| path | true | | Path to your folder with localization files. |
| assetLoader | false | `RootBundleAssetLoader()` | Class loader for localization files. You can use custom loaders from [Easy Localization Loader](https://github.com/aissat/easy_localization_loader) or create your own class. |
| extraAssetLoaders | false | null | A List of asset loaders, in case of needing assets being loaded from a different module or package. (e.g. adding a package that uses [Easy Localization Loader]). |
| fallbackLocale | false | | Returns the locale when the locale is not in the list `supportedLocales`. |
| startLocale | false | | Overrides device locale. |
| saveLocale | false | `true` | Save locale in device storage. |
Expand Down Expand Up @@ -470,6 +471,25 @@ Steps:

4. All done!

### 📦 Localization support for multi module/package project

If you want to add localization support from other modules and packages you can add them via `extraAssetLoaders` parameter:

```dart
void main(){
runApp(EasyLocalization(
child: MyApp(),
supportedLocales: [Locale('en', 'US'), Locale('ar', 'DZ')],
path: 'resources/langs',
assetLoader: CodegenLoader()
extraAssetLoaders: [
TranslationsLoader(packageName: 'package_example_1'),
TranslationsLoader(packageName: 'package_example_2'),
],
));
}
```

### 🔑 Localization keys

If you have many localization keys and are confused, key generation will help you. The code editor will automatically prompt keys
Expand Down
26 changes: 25 additions & 1 deletion lib/src/easy_localization_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,29 @@ class EasyLocalization extends StatefulWidget {
/// You can use custom loaders from [Easy Localization Loader](https://github.com/aissat/easy_localization_loader) or create your own class.
/// @Default value `const RootBundleAssetLoader()`
// ignore: prefer_typing_uninitialized_variables
final assetLoader;
final AssetLoader assetLoader;

/// Class loader for localization files that belong to other packages.
/// You can use custom loaders from [Easy Localization Loader](https://github.com/aissat/easy_localization_loader) or create your own class.
/// Example:
/// ```dart
// runApp(
// EasyLocalization(
// supportedLocales: const <Locale>[
// Locale('en'),
// ],
// fallbackLocale: const Locale('en'),
// assetLoader: const RootBundleAssetLoader(),
// extraAssetLoaders: [
// TranslationsLoader(packageName: 'package_example_1'),
// TranslationsLoader(packageName: 'package_example_2'),
// ],
// path: 'lib/l10n/translations',
// child: const MainApp(),
// ),
// );
/// @Default value `null`
final List<AssetLoader>? extraAssetLoaders;

/// Save locale in device storage.
/// @Default value true
Expand All @@ -86,6 +108,7 @@ class EasyLocalization extends StatefulWidget {
this.useOnlyLangCode = false,
this.useFallbackTranslations = false,
this.assetLoader = const RootBundleAssetLoader(),
this.extraAssetLoaders,
this.saveLocale = true,
this.errorWidget,
}) : assert(supportedLocales.isNotEmpty),
Expand Down Expand Up @@ -126,6 +149,7 @@ class _EasyLocalizationState extends State<EasyLocalization> {
supportedLocales: widget.supportedLocales,
startLocale: widget.startLocale,
assetLoader: widget.assetLoader,
extraAssetLoaders: widget.extraAssetLoaders,
useOnlyLangCode: widget.useOnlyLangCode,
useFallbackTranslations: widget.useFallbackTranslations,
path: widget.path,
Expand Down
49 changes: 39 additions & 10 deletions lib/src/easy_localization_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ class EasyLocalizationController extends ChangeNotifier {
Locale? _fallbackLocale;

final Function(FlutterError e) onLoadError;
// ignore: prefer_typing_uninitialized_variables
final assetLoader;
final AssetLoader assetLoader;
final String path;
final bool useFallbackTranslations;
final bool saveLocale;
final bool useOnlyLangCode;
List<AssetLoader>? extraAssetLoaders;
Translations? _translations, _fallbackTranslations;
Translations? get translations => _translations;
Translations? get fallbackTranslations => _fallbackTranslations;
Expand All @@ -32,6 +32,7 @@ class EasyLocalizationController extends ChangeNotifier {
required this.path,
required this.useOnlyLangCode,
required this.onLoadError,
this.extraAssetLoaders,
Locale? startLocale,
Locale? fallbackLocale,
Locale? forceLocale, // used for testing
Expand Down Expand Up @@ -124,18 +125,46 @@ class EasyLocalizationController extends ChangeNotifier {
return null;
}

Future<Map<String, dynamic>> loadTranslationData(Locale locale) async {
late Map<String, dynamic>? data;
Future<Map<String, dynamic>> loadTranslationData(Locale locale) async =>
_combineAssetLoaders(
path: path,
locale: locale,
assetLoader: assetLoader,
useOnlyLangCode: useOnlyLangCode,
extraAssetLoaders: extraAssetLoaders,
);

if (useOnlyLangCode) {
data = await assetLoader.load(path, Locale(locale.languageCode));
} else {
data = await assetLoader.load(path, locale);
Future<Map<String, dynamic>> _combineAssetLoaders({
required String path,
required Locale locale,
required AssetLoader assetLoader,
required bool useOnlyLangCode,
List<AssetLoader>? extraAssetLoaders,
}) async {
final result = <String, dynamic>{};
final loaderFutures = <Future<Map<String, dynamic>?>>[];

final Locale desiredLocale =
useOnlyLangCode ? Locale(locale.languageCode) : locale;

List<AssetLoader> loaders = [
assetLoader,
if (extraAssetLoaders != null) ...extraAssetLoaders
];

for (final loader in loaders) {
loaderFutures.add(loader.load(path, desiredLocale));
}

if (data == null) return {};
await Future.wait(loaderFutures).then((List<Map<String, dynamic>?> value) {
for (final Map<String, dynamic>? map in value) {
if (map != null) {
result.addAllRecursive(map);
}
}
});

return data;
return result;
}

Locale get locale => _locale;
Expand Down
17 changes: 17 additions & 0 deletions lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,20 @@ extension StringToLocaleHelper on String {
}
}
}

extension MapExtension<K> on Map<K, dynamic> {
void addAllRecursive(Map<K, dynamic> other) {
for (final entry in other.entries) {
final oldValue = this[entry.key];
final newValue = entry.value;

if (oldValue is Map<K, dynamic> && newValue is Map<K, dynamic>) {
oldValue.addAllRecursive(newValue);

continue;
}

this[entry.key] = newValue;
}
}
}
3 changes: 1 addition & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ homepage: https://github.com/aissat/easy_localization
issue_tracker: https://github.com/aissat/easy_localization/issues
# publish_to: none

version: 3.0.4
version: 3.0.5

environment:
sdk: '>=2.12.0 <4.0.0'
Expand All @@ -21,7 +21,6 @@ dependencies:
flutter_localizations:
sdk: flutter


dev_dependencies:
flutter_test:
sdk: flutter
Expand Down
117 changes: 117 additions & 0 deletions test/easy_localization_extra_asset_loaders_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import 'dart:developer';

import 'package:easy_localization/src/easy_localization_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import 'utils/test_asset_loaders.dart';

void main() {
group('ExtraAssetLoaders', () {
test('should work normal if no extraAssetLoaders is provided', () async {
final EasyLocalizationController controller = EasyLocalizationController(
forceLocale: const Locale('en'),
path: 'path/en.json',
supportedLocales: const [Locale('en')],
useOnlyLangCode: true,
useFallbackTranslations: false,
saveLocale: false,
onLoadError: (FlutterError e) {
log(e.toString());
},
assetLoader: const ImmutableJsonAssetLoader(),
extraAssetLoaders: null,
);

var result = await controller.loadTranslationData(const Locale('en'));

expect(result, {'test': 'test'});
expect(result.entries.length, 1);
});

test('load assets from external loader and merge with asset loader',
() async {
final EasyLocalizationController controller = EasyLocalizationController(
forceLocale: const Locale('en'),
path: 'path/en.json',
supportedLocales: const [Locale('en')],
useOnlyLangCode: true,
useFallbackTranslations: false,
saveLocale: false,
onLoadError: (FlutterError e) {
log(e.toString());
},
assetLoader: const ImmutableJsonAssetLoader(),
extraAssetLoaders: [const ExternalAssetLoader()],
);

final Map<String, dynamic> result =
await controller.loadTranslationData(const Locale('en'));

expect(result, {
'test': 'test',
'package_value_01': 'package_value_01',
'package_value_02': 'package_value_02',
'package_value_03': 'package_value_03',
});
expect(result.entries.length, 4);
});

test(
'load assets from external loader with nested translations and merge with asset loader',
() async {
final EasyLocalizationController controller = EasyLocalizationController(
forceLocale: const Locale('en'),
path: 'path/en.json',
supportedLocales: const [Locale('en')],
useOnlyLangCode: true,
useFallbackTranslations: false,
saveLocale: false,
onLoadError: (FlutterError e) {
log(e.toString());
},
assetLoader: const ImmutableJsonAssetLoader(),
extraAssetLoaders: [const NestedAssetLoader()],
);

final Map<String, dynamic> result =
await controller.loadTranslationData(const Locale('en'));

expect(result, {
'test': 'test',
'nested': {
'super': {
'duper': {
'nested': 'nested.super.duper.nested',
}
}
},
});
expect(result.entries.length, 2);
});

test(
'load assets from external loader and merge duplicates with asset loader',
() async {
final EasyLocalizationController controller = EasyLocalizationController(
forceLocale: const Locale('en'),
path: 'path/en.json',
supportedLocales: const [Locale('en')],
useOnlyLangCode: true,
useFallbackTranslations: false,
saveLocale: false,
onLoadError: (FlutterError e) {
log(e.toString());
},
assetLoader: const ImmutableJsonAssetLoader(),
extraAssetLoaders: [const ImmutableJsonAssetLoader()],
);

final Map<String, dynamic> result =
await controller.loadTranslationData(const Locale('en'));

expect(result, {'test': 'test'});
expect(result.entries.length, 1);
});
});
}
53 changes: 53 additions & 0 deletions test/easy_localization_utils_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,58 @@ void main() {
expect(string, 'zh|Hant|HK');
});
});

group('MapExtension', () {
test('should add all key value pairs recursively', () {
final Map<String, dynamic> map1 = {
'key1': 'value1',
'key2': {
'key3': 'value3',
'key4': 'value4',
},
};

final Map<String, dynamic> map2 = {
'key2': {
'key4': 'new_value4',
'key5': 'value5',
},
'key6': 'value6',
};

map1.addAllRecursive(map2);

expect(map1, {
'key1': 'value1',
'key2': {
'key3': 'value3',
'key4': 'new_value4',
'key5': 'value5',
},
'key6': 'value6',
});
});

test('should work with empty maps', () {
final Map<String, dynamic> map1 = {};
final Map<String, dynamic> map2 = {
'key1': 'value1',
'key2': {
'key3': 'value3',
'key4': 'value4',
},
};

map1.addAllRecursive(map2);

expect(map1, {
'key1': 'value1',
'key2': {
'key3': 'value3',
'key4': 'value4',
},
});
});
});
});
}
Loading

0 comments on commit 63a3974

Please sign in to comment.