Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4ce6054
feat(v4): update example app
aissat Sep 10, 2023
bba4524
refactoring `AssetLoader` and `RootBundleAssetLoader`
aissat Sep 25, 2023
a784051
feat: create easy_localization_storage_interface.dart
aissat Sep 25, 2023
7d30d13
update easy_localization_app.dart
aissat Sep 25, 2023
cb289d1
add export storage interface
aissat Sep 25, 2023
e8a7c54
update `AssetLoader` docs
aissat Sep 25, 2023
4af688a
refactoring `EasyLocalizationController` update `initEasyLocation` su…
aissat Sep 25, 2023
e982c5a
update generate.dart
aissat Sep 25, 2023
28de687
chore: update pubspec.yaml
aissat Sep 25, 2023
ea585d1
chore: create ar.json, ar.json and en.json
aissat Sep 25, 2023
b5a12c3
feat(test) :update test
aissat Sep 25, 2023
718f3b4
Add caching support to RootBundleAssetLoader
aissat Jun 9, 2026
03df779
Refactor localization controller and improve translation loading
aissat Jun 9, 2026
e989afb
Update dependencies and SDK syntax
aissat Jun 9, 2026
ada9d32
Add performance benchmarking and feedback to locale switch
aissat Jun 9, 2026
b509d2d
Improve translation loading and cache safety
aissat Jun 9, 2026
43e0d0a
Refactor asset loader tests and improve controller initialization
aissat Jun 9, 2026
3c225c4
Make storage interface methods asynchronous
aissat Jun 9, 2026
a02b3e6
Update documentation and API for pluggable storage
aissat Jun 9, 2026
d7e7682
Add compatibility extension for v3 migration
aissat Jun 9, 2026
5e4717e
Add null check to asset loader and add compatibility tests
aissat Jun 9, 2026
2fa3145
Update widget tests for localization configuration
aissat Jun 9, 2026
ed6453f
Update changelog for 4.0.0-dev release
aissat Jun 9, 2026
3d9fd0a
Fix formatting and clean up metadata in CHANGELOG and pubspec
aissat Jun 9, 2026
35ef12a
Add .pubignore file to exclude unnecessary files
aissat Jun 9, 2026
2445fac
fix: untrack .iml .metadata, unignore project.pbxproj from gitignore
aissat Jun 9, 2026
6ee83c0
Remove ephemeral Flutter iOS configuration files
aissat Jun 9, 2026
b7d6ed7
Remove localization-kit from topics in pubspec.yaml
aissat Jun 9, 2026
416935f
Refactor asset loading and storage initialization
aissat Jun 9, 2026
3803012
Fix asset loader tests
aissat Jun 9, 2026
138dea3
Update SDK constraint to support older versions
aissat Jun 9, 2026
17dbe70
Update device locale detection to use PlatformDispatcher
aissat Jun 9, 2026
feefc72
Update version to 4.0.0-dev and document breaking changes
aissat Jun 9, 2026
d2da41b
Add library documentation and refine exports
aissat Jun 9, 2026
35322af
Add pure resolver functions for translation data
aissat Jun 13, 2026
d94ba7c
Add unit tests for resolver functions
aissat Jun 13, 2026
255088e
Clean up test files
aissat Jun 13, 2026
1e59e00
Update SDK constraints and add matcher dependency
aissat Jun 13, 2026
389130b
Fix empty string handling in capitalize and remove unused dependency
aissat Jun 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/generate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ class CodegenLoader extends AssetLoader{
const CodegenLoader();

@override
Future<Map<String, dynamic>?> load(String path, Locale locale) {
Future<Map<String, dynamic>> load({Locale? locale}) {
return Future.value(mapLocales[locale.toString()]);
}

Expand Down
8 changes: 6 additions & 2 deletions example/lib/generated/codegen_loader.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import 'dart:ui';

import 'package:easy_localization/easy_localization.dart' show AssetLoader;

class CodegenLoader extends AssetLoader {
class CodegenLoader implements AssetLoader {
const CodegenLoader();

@override
Future<Map<String, dynamic>> load(String fullPath, Locale locale) {
Future<Map<String, dynamic>> load(Locale locale) {
return Future.value(mapLocales[locale.toString()]);
}

Expand Down Expand Up @@ -310,4 +310,8 @@ class CodegenLoader extends AssetLoader {
"ru_RU": ru_RU,
"ru": ru
};

@override
// TODO: implement path
String get path => throw UnimplementedError();
}
11 changes: 6 additions & 5 deletions example/lib/lang_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class LanguageView extends StatelessWidget {
'',
style: TextStyle(color: Colors.black),
),
backgroundColor: Colors.white,
// backgroundColor: Colors.white,
iconTheme: IconThemeData(color: Colors.black),
elevation: 0,
),
Expand All @@ -29,7 +29,7 @@ class LanguageView extends StatelessWidget {
child: Text(
'Choose language',
style: TextStyle(
color: Colors.blue,
color: Theme.of(context).primaryColor,
fontFamily: 'Montserrat',
fontWeight: FontWeight.w700,
fontSize: 18,
Expand Down Expand Up @@ -98,10 +98,11 @@ class _SwitchListTileMenuItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(left: 10, right: 10, top: 5),
margin: EdgeInsets.symmetric(vertical: 2, horizontal: 24),
decoration: BoxDecoration(
border:
isSelected(context) ? Border.all(color: Colors.blueAccent) : null,
border: isSelected(context)
? Border.all(color: Theme.of(context).primaryColor)
: null,
),
child: ListTile(
dense: true,
Expand Down
36 changes: 17 additions & 19 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,8 @@ import 'lang_view.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();

runApp(EasyLocalization(
supportedLocales: [
Locale('en', 'US'),
Locale('ar', 'DZ'),
Locale('de', 'DE'),
Locale('ru', 'RU')
],
path: 'resources/langs',
child: MyApp(),
// fallbackLocale: Locale('en', 'US'),
// startLocale: Locale('de', 'DE'),
// saveLocale: false,
// useOnlyLangCode: true,

await EasyLocalization.ensureInitialized(
assetLoader: RootBundleAssetLoader(path: 'resources/langs/json'),
// optional assetLoader default used is RootBundleAssetLoader which uses flutter's assetloader
// install easy_localization_loader for enable custom loaders
// assetLoader: RootBundleAssetLoader()
Expand All @@ -35,18 +21,30 @@ void main() async {
// assetLoader: XmlAssetLoader() //multiple files
// assetLoader: XmlSingleAssetLoader() //single file
// assetLoader: CodegenLoader()
));
);

runApp(
EasyLocalization(
child: MyApp(),
// fallbackLocale: Locale('en', 'US'),
// startLocale: Locale('ar', 'DZ'),
supportedLocales: [Locale('en', 'US'), Locale('ar', 'DZ')],

Copilot AI Aug 11, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The supportedLocales parameter is being used in the EasyLocalization widget, but according to the migration guide, this parameter should be removed in v4.x.

Suggested change
supportedLocales: [Locale('en', 'US'), Locale('ar', 'DZ')],

Copilot uses AI. Check for mistakes.
saveLocale: false,
// useOnlyLangCode: true,
),
);
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('supportedLocales => ${context.supportedLocales}');
return MaterialApp(
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: MyHomePage(title: 'Easy localization'),
);
Expand Down Expand Up @@ -94,7 +92,7 @@ class _MyHomePageState extends State<MyHomePage> {
},
child: Icon(
Icons.language,
color: Colors.white,
// color: Colors.white,
),
),
],
Expand Down
2 changes: 1 addition & 1 deletion example/linux/flutter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
linux-x64 ${CMAKE_BUILD_TYPE}
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
Expand Down
6 changes: 5 additions & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ publish_to: none
version: 1.0.0+1

environment:
sdk: ">=2.12.0-0 <4.0.0"
sdk: ">=3.1.0 <4.0.0"

dependencies:
flutter:
Expand Down Expand Up @@ -49,6 +49,10 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- resources/langs/
- resources/langs/json/
- resources/langs/yml/
- resources/langs/xml/

# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
22 changes: 11 additions & 11 deletions example/test/widget_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ import 'package:example/main.dart';

void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// // Build our app and trigger a frame.
// await tester.pumpWidget(MyApp());

// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// // Verify that our counter starts at 0.
Comment on lines 14 to +18
// expect(find.text('0'), findsOneWidget);
// expect(find.text('1'), findsNothing);

// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// // Tap the '+' icon and trigger a frame.
// await tester.tap(find.byIcon(Icons.add));
// await tester.pump();

// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
// // Verify that our counter has incremented.
// expect(find.text('0'), findsNothing);
// expect(find.text('1'), findsOneWidget);
});
}
11 changes: 11 additions & 0 deletions i18n/ar/ar.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"test": "اختبار",
"day": {
"zero": "{} يوم",
"one": "{} يوم",
"two": "{} أيام",
"few": "{} أيام",
"many": "{} يوم",
"other": "{} يوم"
}
}
11 changes: 11 additions & 0 deletions i18n/ar_en/ar.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"test": "اختبار",
"day": {
"zero": "{} يوم",
"one": "{} يوم",
"two": "{} أيام",
"few": "{} أيام",
"many": "{} يوم",
"other": "{} يوم"
}
}
22 changes: 22 additions & 0 deletions i18n/ar_en/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"test": "test",
"day": {
"zero": "{} days",
"one": "{} day",
"two": "{} days",
"few": "{} few days",
"many": "{} many days",
"other": "{} other days"
},
"hat": {
"zero": "no hats",
"one": "one hat",
"two": "two hats",
"few": "few hats",
"many": "many hats",
"other": "other hats"
},
"hat_other": {
"other": "other hats"
}
}
1 change: 1 addition & 0 deletions lib/easy_localization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export 'package:easy_localization/src/easy_localization_app.dart';
export 'package:easy_localization/src/asset_loader.dart';
export 'package:easy_localization/src/public.dart';
export 'package:easy_localization/src/public_ext.dart';
export 'package:easy_localization/src/easy_localization_storage_interface.dart';
export 'package:intl/intl.dart';
66 changes: 47 additions & 19 deletions lib/src/asset_loader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,64 @@ import 'dart:ui';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';

/// abstract class used to building your Custom AssetLoader
/// Example:
/// ```
///class FileAssetLoader extends AssetLoader {
/// @override
/// Future<Map<String, dynamic>> load(String path, Locale locale) async {
/// final file = File(path);
/// return json.decode(await file.readAsString());
/// }
///}
/// ```
/// Abstract class for loading assets.
abstract class AssetLoader {
const AssetLoader();
Future<Map<String, dynamic>?> load(String path, Locale locale);
/// Path to the assets directory.
/// Example:
/// ```dart
/// path: 'assets/translations',
/// path: 'assets/translations/lang.csv',
/// ```
final String? path;

/// List of supported locales.
/// {@macro flutter.widgets.widgetsApp.supportedLocales}
final List<Locale>? supportedLocales;

/// Constructor for [AssetLoader].
///
/// [path] is the path to the assets directory.
/// [supportedLocales] is a list of locales that the assets support.
const AssetLoader({this.path, this.supportedLocales})
: assert(path != null || supportedLocales != null,
'path or supportedLocales must not be null');

Copilot AI Aug 11, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertion message could be more descriptive. Consider changing it to 'Either path or supportedLocales must be provided' for better developer experience.

Suggested change
'path or supportedLocales must not be null');
'Either path or supportedLocales must be provided');

Copilot uses AI. Check for mistakes.

/// Loads the assets for the given [locale].
///
/// Returns a map of loaded assets.
Future<Map<String, dynamic>> load({Locale? locale});
Comment on lines +8 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider making locale required in load() method.

While the class structure and documentation look good, making locale optional in the load() method seems inconsistent since implementations like RootBundleAssetLoader require it and throw an error if it's null.

Consider updating the method signature to:

- Future<Map<String, dynamic>> load({Locale? locale});
+ Future<Map<String, dynamic>> load({required Locale locale});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Abstract class for loading assets.
abstract class AssetLoader {
const AssetLoader();
Future<Map<String, dynamic>?> load(String path, Locale locale);
/// Path to the assets directory.
/// Example:
/// ```dart
/// path: 'assets/translations',
/// path: 'assets/translations/lang.csv',
/// ```
final String? path;
/// List of supported locales.
/// {@macro flutter.widgets.widgetsApp.supportedLocales}
final List<Locale>? supportedLocales;
/// Constructor for [AssetLoader].
///
/// [path] is the path to the assets directory.
/// [supportedLocales] is a list of locales that the assets support.
const AssetLoader({this.path, this.supportedLocales})
: assert(path != null || supportedLocales != null,
'path or supportedLocales must not be null');
/// Loads the assets for the given [locale].
///
/// Returns a map of loaded assets.
Future<Map<String, dynamic>> load({Locale? locale});
/// Abstract class for loading assets.
abstract class AssetLoader {
/// Path to the assets directory.
/// Example:

}

///
/// default used is RootBundleAssetLoader which uses flutter's assetloader
/// The `RootBundleAssetLoader` class is a subclass of `AssetLoader` that uses Flutter's asset loader
/// to load localized JSON files.
///
class RootBundleAssetLoader extends AssetLoader {
const RootBundleAssetLoader();
// A custom asset loader that loads assets from the root bundle

String getLocalePath(String basePath, Locale locale) {
return '$basePath/${locale.toStringWithSeparator(separator: "-")}.json';
const RootBundleAssetLoader(String path) : super(path: path);

/// Returns the path for the specified locale
///
/// The [locale] parameter represents the desired locale.
/// The returned path is based on the [path] of the asset loader
/// and the [locale] with a separator ("-") between language and country.
String getLocalePath(Locale locale) {
return '$path/${locale.toStringWithSeparator(separator: "-")}.json';
}
Comment on lines +90 to 96

///
/// Loads the localized JSON file for the given `locale`.
///
/// Throws an `ArgumentError` if the `locale` is `null`.
///
@override
Future<Map<String, dynamic>?> load(String path, Locale locale) async {
var localePath = getLocalePath(path, locale);
Future<Map<String, dynamic>> load({Locale? locale}) async {
if (locale == null) throw ArgumentError.notNull('locale');
var localePath = getLocalePath(locale);
EasyLocalization.logger.debug('Load asset from $path');

// Load the asset as a string and decode it as JSON
return json.decode(await rootBundle.loadString(localePath));
}
Comment on lines +76 to 128

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for JSON parsing and file loading.

The implementation should handle potential errors when loading and parsing JSON files.

Consider adding try-catch blocks:

 @override
 Future<Map<String, dynamic>> load({Locale? locale}) async {
   if (locale == null) throw ArgumentError.notNull('locale');
   var localePath = getLocalePath(locale);
   EasyLocalization.logger.debug('Load asset from $path');

-  // Load the asset as a string and decode it as JSON
-  return json.decode(await rootBundle.loadString(localePath));
+  try {
+    final jsonString = await rootBundle.loadString(localePath);
+    return json.decode(jsonString) as Map<String, dynamic>;
+  } on FlutterError catch (e) {
+    throw AssetLoadException('Failed to load $localePath: ${e.message}');
+  } on FormatException catch (e) {
+    throw AssetLoadException('Invalid JSON in $localePath: ${e.message}');
+  }
 }

Committable suggestion skipped: line range outside the PR's diff.

}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading
Loading