Skip to content

Commit 63a3974

Browse files
authored
add extra asset loaders (#654)
* add extra asset loaders * make some changes based on code review * minor refactor
1 parent 76484a9 commit 63a3974

9 files changed

+302
-13
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
### [3.0.5]
4+
5+
- add 'extraAssetLoaders' to add more assets loaders if it is needed, for example, if you want to add packages localizations to your project.
6+
37
### [3.0.4]
48

59
- determine plural cases based on the actual language rules (#620)

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ class MyApp extends StatelessWidget {
141141
| supportedLocales | true | | List of supported locales. |
142142
| path | true | | Path to your folder with localization files. |
143143
| 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. |
144+
| 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]). |
144145
| fallbackLocale | false | | Returns the locale when the locale is not in the list `supportedLocales`. |
145146
| startLocale | false | | Overrides device locale. |
146147
| saveLocale | false | `true` | Save locale in device storage. |
@@ -470,6 +471,25 @@ Steps:
470471

471472
4. All done!
472473

474+
### 📦 Localization support for multi module/package project
475+
476+
If you want to add localization support from other modules and packages you can add them via `extraAssetLoaders` parameter:
477+
478+
```dart
479+
void main(){
480+
runApp(EasyLocalization(
481+
child: MyApp(),
482+
supportedLocales: [Locale('en', 'US'), Locale('ar', 'DZ')],
483+
path: 'resources/langs',
484+
assetLoader: CodegenLoader()
485+
extraAssetLoaders: [
486+
TranslationsLoader(packageName: 'package_example_1'),
487+
TranslationsLoader(packageName: 'package_example_2'),
488+
],
489+
));
490+
}
491+
```
492+
473493
### 🔑 Localization keys
474494

475495
If you have many localization keys and are confused, key generation will help you. The code editor will automatically prompt keys

lib/src/easy_localization_app.dart

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,29 @@ class EasyLocalization extends StatefulWidget {
6666
/// You can use custom loaders from [Easy Localization Loader](https://github.com/aissat/easy_localization_loader) or create your own class.
6767
/// @Default value `const RootBundleAssetLoader()`
6868
// ignore: prefer_typing_uninitialized_variables
69-
final assetLoader;
69+
final AssetLoader assetLoader;
70+
71+
/// Class loader for localization files that belong to other packages.
72+
/// You can use custom loaders from [Easy Localization Loader](https://github.com/aissat/easy_localization_loader) or create your own class.
73+
/// Example:
74+
/// ```dart
75+
// runApp(
76+
// EasyLocalization(
77+
// supportedLocales: const <Locale>[
78+
// Locale('en'),
79+
// ],
80+
// fallbackLocale: const Locale('en'),
81+
// assetLoader: const RootBundleAssetLoader(),
82+
// extraAssetLoaders: [
83+
// TranslationsLoader(packageName: 'package_example_1'),
84+
// TranslationsLoader(packageName: 'package_example_2'),
85+
// ],
86+
// path: 'lib/l10n/translations',
87+
// child: const MainApp(),
88+
// ),
89+
// );
90+
/// @Default value `null`
91+
final List<AssetLoader>? extraAssetLoaders;
7092

7193
/// Save locale in device storage.
7294
/// @Default value true
@@ -86,6 +108,7 @@ class EasyLocalization extends StatefulWidget {
86108
this.useOnlyLangCode = false,
87109
this.useFallbackTranslations = false,
88110
this.assetLoader = const RootBundleAssetLoader(),
111+
this.extraAssetLoaders,
89112
this.saveLocale = true,
90113
this.errorWidget,
91114
}) : assert(supportedLocales.isNotEmpty),
@@ -126,6 +149,7 @@ class _EasyLocalizationState extends State<EasyLocalization> {
126149
supportedLocales: widget.supportedLocales,
127150
startLocale: widget.startLocale,
128151
assetLoader: widget.assetLoader,
152+
extraAssetLoaders: widget.extraAssetLoaders,
129153
useOnlyLangCode: widget.useOnlyLangCode,
130154
useFallbackTranslations: widget.useFallbackTranslations,
131155
path: widget.path,

lib/src/easy_localization_controller.dart

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ class EasyLocalizationController extends ChangeNotifier {
1414
Locale? _fallbackLocale;
1515

1616
final Function(FlutterError e) onLoadError;
17-
// ignore: prefer_typing_uninitialized_variables
18-
final assetLoader;
17+
final AssetLoader assetLoader;
1918
final String path;
2019
final bool useFallbackTranslations;
2120
final bool saveLocale;
2221
final bool useOnlyLangCode;
22+
List<AssetLoader>? extraAssetLoaders;
2323
Translations? _translations, _fallbackTranslations;
2424
Translations? get translations => _translations;
2525
Translations? get fallbackTranslations => _fallbackTranslations;
@@ -32,6 +32,7 @@ class EasyLocalizationController extends ChangeNotifier {
3232
required this.path,
3333
required this.useOnlyLangCode,
3434
required this.onLoadError,
35+
this.extraAssetLoaders,
3536
Locale? startLocale,
3637
Locale? fallbackLocale,
3738
Locale? forceLocale, // used for testing
@@ -124,18 +125,46 @@ class EasyLocalizationController extends ChangeNotifier {
124125
return null;
125126
}
126127

127-
Future<Map<String, dynamic>> loadTranslationData(Locale locale) async {
128-
late Map<String, dynamic>? data;
128+
Future<Map<String, dynamic>> loadTranslationData(Locale locale) async =>
129+
_combineAssetLoaders(
130+
path: path,
131+
locale: locale,
132+
assetLoader: assetLoader,
133+
useOnlyLangCode: useOnlyLangCode,
134+
extraAssetLoaders: extraAssetLoaders,
135+
);
129136

130-
if (useOnlyLangCode) {
131-
data = await assetLoader.load(path, Locale(locale.languageCode));
132-
} else {
133-
data = await assetLoader.load(path, locale);
137+
Future<Map<String, dynamic>> _combineAssetLoaders({
138+
required String path,
139+
required Locale locale,
140+
required AssetLoader assetLoader,
141+
required bool useOnlyLangCode,
142+
List<AssetLoader>? extraAssetLoaders,
143+
}) async {
144+
final result = <String, dynamic>{};
145+
final loaderFutures = <Future<Map<String, dynamic>?>>[];
146+
147+
final Locale desiredLocale =
148+
useOnlyLangCode ? Locale(locale.languageCode) : locale;
149+
150+
List<AssetLoader> loaders = [
151+
assetLoader,
152+
if (extraAssetLoaders != null) ...extraAssetLoaders
153+
];
154+
155+
for (final loader in loaders) {
156+
loaderFutures.add(loader.load(path, desiredLocale));
134157
}
135158

136-
if (data == null) return {};
159+
await Future.wait(loaderFutures).then((List<Map<String, dynamic>?> value) {
160+
for (final Map<String, dynamic>? map in value) {
161+
if (map != null) {
162+
result.addAllRecursive(map);
163+
}
164+
}
165+
});
137166

138-
return data;
167+
return result;
139168
}
140169

141170
Locale get locale => _locale;

lib/src/utils.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,20 @@ extension StringToLocaleHelper on String {
6161
}
6262
}
6363
}
64+
65+
extension MapExtension<K> on Map<K, dynamic> {
66+
void addAllRecursive(Map<K, dynamic> other) {
67+
for (final entry in other.entries) {
68+
final oldValue = this[entry.key];
69+
final newValue = entry.value;
70+
71+
if (oldValue is Map<K, dynamic> && newValue is Map<K, dynamic>) {
72+
oldValue.addAllRecursive(newValue);
73+
74+
continue;
75+
}
76+
77+
this[entry.key] = newValue;
78+
}
79+
}
80+
}

pubspec.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ homepage: https://github.com/aissat/easy_localization
55
issue_tracker: https://github.com/aissat/easy_localization/issues
66
# publish_to: none
77

8-
version: 3.0.4
8+
version: 3.0.5
99

1010
environment:
1111
sdk: '>=2.12.0 <4.0.0'
@@ -21,7 +21,6 @@ dependencies:
2121
flutter_localizations:
2222
sdk: flutter
2323

24-
2524
dev_dependencies:
2625
flutter_test:
2726
sdk: flutter
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import 'dart:developer';
2+
3+
import 'package:easy_localization/src/easy_localization_controller.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:flutter_test/flutter_test.dart';
6+
7+
import 'utils/test_asset_loaders.dart';
8+
9+
void main() {
10+
group('ExtraAssetLoaders', () {
11+
test('should work normal if no extraAssetLoaders is provided', () async {
12+
final EasyLocalizationController controller = EasyLocalizationController(
13+
forceLocale: const Locale('en'),
14+
path: 'path/en.json',
15+
supportedLocales: const [Locale('en')],
16+
useOnlyLangCode: true,
17+
useFallbackTranslations: false,
18+
saveLocale: false,
19+
onLoadError: (FlutterError e) {
20+
log(e.toString());
21+
},
22+
assetLoader: const ImmutableJsonAssetLoader(),
23+
extraAssetLoaders: null,
24+
);
25+
26+
var result = await controller.loadTranslationData(const Locale('en'));
27+
28+
expect(result, {'test': 'test'});
29+
expect(result.entries.length, 1);
30+
});
31+
32+
test('load assets from external loader and merge with asset loader',
33+
() async {
34+
final EasyLocalizationController controller = EasyLocalizationController(
35+
forceLocale: const Locale('en'),
36+
path: 'path/en.json',
37+
supportedLocales: const [Locale('en')],
38+
useOnlyLangCode: true,
39+
useFallbackTranslations: false,
40+
saveLocale: false,
41+
onLoadError: (FlutterError e) {
42+
log(e.toString());
43+
},
44+
assetLoader: const ImmutableJsonAssetLoader(),
45+
extraAssetLoaders: [const ExternalAssetLoader()],
46+
);
47+
48+
final Map<String, dynamic> result =
49+
await controller.loadTranslationData(const Locale('en'));
50+
51+
expect(result, {
52+
'test': 'test',
53+
'package_value_01': 'package_value_01',
54+
'package_value_02': 'package_value_02',
55+
'package_value_03': 'package_value_03',
56+
});
57+
expect(result.entries.length, 4);
58+
});
59+
60+
test(
61+
'load assets from external loader with nested translations and merge with asset loader',
62+
() async {
63+
final EasyLocalizationController controller = EasyLocalizationController(
64+
forceLocale: const Locale('en'),
65+
path: 'path/en.json',
66+
supportedLocales: const [Locale('en')],
67+
useOnlyLangCode: true,
68+
useFallbackTranslations: false,
69+
saveLocale: false,
70+
onLoadError: (FlutterError e) {
71+
log(e.toString());
72+
},
73+
assetLoader: const ImmutableJsonAssetLoader(),
74+
extraAssetLoaders: [const NestedAssetLoader()],
75+
);
76+
77+
final Map<String, dynamic> result =
78+
await controller.loadTranslationData(const Locale('en'));
79+
80+
expect(result, {
81+
'test': 'test',
82+
'nested': {
83+
'super': {
84+
'duper': {
85+
'nested': 'nested.super.duper.nested',
86+
}
87+
}
88+
},
89+
});
90+
expect(result.entries.length, 2);
91+
});
92+
93+
test(
94+
'load assets from external loader and merge duplicates with asset loader',
95+
() async {
96+
final EasyLocalizationController controller = EasyLocalizationController(
97+
forceLocale: const Locale('en'),
98+
path: 'path/en.json',
99+
supportedLocales: const [Locale('en')],
100+
useOnlyLangCode: true,
101+
useFallbackTranslations: false,
102+
saveLocale: false,
103+
onLoadError: (FlutterError e) {
104+
log(e.toString());
105+
},
106+
assetLoader: const ImmutableJsonAssetLoader(),
107+
extraAssetLoaders: [const ImmutableJsonAssetLoader()],
108+
);
109+
110+
final Map<String, dynamic> result =
111+
await controller.loadTranslationData(const Locale('en'));
112+
113+
expect(result, {'test': 'test'});
114+
expect(result.entries.length, 1);
115+
});
116+
});
117+
}

test/easy_localization_utils_test.dart

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,58 @@ void main() {
5454
expect(string, 'zh|Hant|HK');
5555
});
5656
});
57+
58+
group('MapExtension', () {
59+
test('should add all key value pairs recursively', () {
60+
final Map<String, dynamic> map1 = {
61+
'key1': 'value1',
62+
'key2': {
63+
'key3': 'value3',
64+
'key4': 'value4',
65+
},
66+
};
67+
68+
final Map<String, dynamic> map2 = {
69+
'key2': {
70+
'key4': 'new_value4',
71+
'key5': 'value5',
72+
},
73+
'key6': 'value6',
74+
};
75+
76+
map1.addAllRecursive(map2);
77+
78+
expect(map1, {
79+
'key1': 'value1',
80+
'key2': {
81+
'key3': 'value3',
82+
'key4': 'new_value4',
83+
'key5': 'value5',
84+
},
85+
'key6': 'value6',
86+
});
87+
});
88+
89+
test('should work with empty maps', () {
90+
final Map<String, dynamic> map1 = {};
91+
final Map<String, dynamic> map2 = {
92+
'key1': 'value1',
93+
'key2': {
94+
'key3': 'value3',
95+
'key4': 'value4',
96+
},
97+
};
98+
99+
map1.addAllRecursive(map2);
100+
101+
expect(map1, {
102+
'key1': 'value1',
103+
'key2': {
104+
'key3': 'value3',
105+
'key4': 'value4',
106+
},
107+
});
108+
});
109+
});
57110
});
58111
}

0 commit comments

Comments
 (0)