Skip to content

v4.0.0-dev — API refactor, caching, fallback fixes, and benchmark suite#742

Open
aissat wants to merge 39 commits into
developfrom
V4
Open

v4.0.0-dev — API refactor, caching, fallback fixes, and benchmark suite#742
aissat wants to merge 39 commits into
developfrom
V4

Conversation

@aissat

@aissat aissat commented Jan 26, 2025

Copy link
Copy Markdown
Owner

v4.0.0-dev — API refactor, caching, fallback fixes, and benchmark suite

Summary

This PR modernizes the easy_localization API by moving path and supportedLocales into AssetLoader, adding built-in caching, introducing a clean storage interface with SharedPreferencesStorage and InMemoryStorage, fixing fallback translation bugs, and introducing a comprehensive benchmark suite (v3 vs v4).


⚠️ Breaking Changes

AssetLoader rework

AssetLoader is no longer a stateless pass-through. It now owns path and supportedLocales, passed via constructor:

// v3 — stateless, path on widget
abstract class AssetLoader {
  Future<Map<String, dynamic>> load(String localePath);
}

// v4 — owns path + locales
abstract class AssetLoader {
  final String? path;
  final List<Locale>? supportedLocales;
  const AssetLoader({this.path, this.supportedLocales});

  Future<Map<String, dynamic>> load({Locale? locale});
}

RootBundleAssetLoader requires path

// v3
const RootBundleAssetLoader()

// v4
const RootBundleAssetLoader(path: 'assets/translations')

EasyLocalization widget simplified

ssupportedLocales`` and path are no longer widget properties — they live on the loader. The widget only receives `child`, `fallbackLocale`, `startLocale`, `saveLocale`, `useFallbackTranslations`, `useOnlyLangCode`, and `errorWidget`.

ensureInitialized() is now required, takes loader + optional storage

await EasyLocalization.ensureInitialized(
  assetLoader: const RootBundleAssetLoader(path: 'assets/translations'),
  storage: const SharedPreferencesStorage(), // optional, defaults to SharedPreferencesStorage
);

Removed APIs

API Reason
useFallbackTranslationsForEmptyResources Unclear semantics, rarely used
ignorePluralRules Relied on removed ICU data
extraAssetLoaders Complex chaining, better solved by custom AssetLoader
AssetLoader.localeExists() load() throws on failure instead

🗄️ Storage Interface

Provided a clean, testable storage abstraction with two implementations:

IEasyLocalizationStorage

abstract class IEasyLocalizationStorage {
  Future<void> init();
  Future<String?> getValue(String key);
  Future<void> setValue(String key, String value);
  Future<void> removeValue(String key);
  Future<void> close();
}

SharedPreferencesStorage (default)

Backed by shared_preferences. Pass to ensureInitialized():

await EasyLocalization.ensureInitialized(
  assetLoader: const RootBundleAssetLoader(path: 'assets/translations'),
  storage: const SharedPreferencesStorage(),
);

InMemoryStorage (testing)

Ephemeral map-based storage for unit tests — no mock setup needed:

final storage = InMemoryStorage();
await EasyLocalization.ensureInitialized(
  assetLoader: const RootBundleAssetLoader(path: 'assets/translations'),
  storage: storage,
);

print(await storage.getValue('locale')); // null
await storage.setValue('locale', 'en_US');
print(await storage.getValue('locale')); // 'en_US'

✨ New Features

Built-in caching

Three caching layers available:

Class Description
CachedAssetLoader Base class with static _translationCache, cacheTranslations(), getCachedTranslations(), isCached(). Building block for custom loaders.
OptimizedAssetLoader Extends RootBundleAssetLoader with a separate static cache layer + cache-hit logging.
RootBundleAssetLoader Now caches translations per locale in a static _cache internally. Switching back to a previously loaded locale costs ~0.02–0.09 ms.

Interactive switch timing

When running flutter run and tapping a language:

  • SnackBar: Switch time: 0.56 ms
  • On-screen: Last switch: X.XX ms on the main page
  • Logs: BENCH switch (interactive: en_US) 0.56 ms

Benchmark suite

flutter_benchmark/ contains identical Flutter Linux apps for v3.0.8 vs v4.0.0-dev.

Run with: cd flutter_benchmark && bash bench.sh

Sample results (Linux x64 release AOT):

Benchmark v3 (ms) v4 (ms) Winner
Startup → first frame 982.41 976.22 ≈ tie
Cold setLocale() 3.69 3.76 ≈ tie
Switch avg (10 rounds) 0.63 0.06 ▲ v4 10× faster

🐛 Bug Fixes

Fallback translations overwritten by base language

When _locale has a country code (e.g. es_AR) and supportedLocales contains the base language (es), the base language data was merged after the fallback locale data, overwriting real fallback values with null:

// BAD (v3): base data clobbered fallback
fallbackData.addAll(baseData);

// FIX (v4): skip nulls
baseData.forEach((k, v) {
  if (v != null) fallbackData.putIfAbsent(k, () => v);
});

useOnlyLangCode with country-code locales

_locale was static — all controller instances shared it, and later constructor calls overwrote it before loadTranslations() ran. Restructured tests to create + load each controller within the same microtask.

CachedAssetLoader missing const constructor

Added const CachedAssetLoader({required super.path}) for use in const contexts.

ListTile ink-splash warnings

Wrapped Container + ListTile in Material(type: MaterialType.transparency) across all benchmarks and example.


🧪 Test Improvements

  • Rewrote test/easy_localization_test.dart (~175 → ~600 lines)
  • New test groups: Asset loader, wrong path, linked locale messages, nested arguments, fallback edge cases
  • All tests use explicit EasyLocalizationController instances (no global singleton)
  • Fallback tests cover: base-language merge, missing fallback keys, putIfAbsent null-skip

Migration Guide

Step 1: Update main()

// Before (v3)
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(EasyLocalization(
    child: MyApp(),
    supportedLocales: [Locale('en')],
    path: 'assets/translations',
  ));
}

// After (v4)
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized(
    assetLoader: const RootBundleAssetLoader(path: 'assets/translations'),
  );
  runApp(EasyLocalization(child: MyApp()));
}

Step 2: Update custom asset loaders

// Before (v3)
class MyLoader extends AssetLoader {
  @override
  Future<Map<String, dynamic>> load(String path, Locale locale) async {
    return json.decode(
      await File('$path/${locale.languageCode}.json').readAsString(),
    );
  }
}

// After (v4)
class MyLoader extends AssetLoader {
  const MyLoader({required String path}) : super(path: path);

  @override
  Future<Map<String, dynamic>> load({Locale? locale}) async {
    return json.decode(
      await File('$path/${locale!.languageCode}.json').readAsString(),
    );
  }
}

Summary by CodeRabbit

  • New Features

    • Pluggable asset loaders with built-in caching options and a new persistent/in-memory storage setup.
    • Migration helpers for older configurations, plus new Arabic locale assets.
    • Added Playwright CI workflow for end-to-end testing.
  • Breaking Changes

    • Updated initialization flow to require an asset loader/storage configuration; loader API and supported locales handling changed, alongside major package/SDK constraint updates.
  • Improvements

    • Enhanced pluralization fallback behavior and more informative translation-load errors.
    • Theme-related widget tweaks and updated default error text.
  • Bug Fixes

    • Improved translation existence checks across fallback data.

@coderabbitai

coderabbitai Bot commented Jan 26, 2025

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f0d4737a-d0c8-4c21-bbd4-89290d98a741

📥 Commits

Reviewing files that changed from the base of the PR and between 820c9d8 and 389130b.

⛔ Files ignored due to path filters (1)
  • example/lib/generated/codegen_loader.g.dart is excluded by !**/generated/**
📒 Files selected for processing (58)
  • .github/workflows/playwright.yml
  • .gitignore
  • CHANGELOG.md
  • README.md
  • bin/generate.dart
  • easy_localization.iml
  • example/.metadata
  • example/lib/lang_view.dart
  • example/lib/main.dart
  • example/linux/flutter/CMakeLists.txt
  • example/pubspec.yaml
  • example/resources/langs/json/ar-DZ.json
  • example/resources/langs/json/ar.json
  • example/resources/langs/json/de-DE.json
  • example/resources/langs/json/de.json
  • example/resources/langs/json/en-US.json
  • example/resources/langs/json/en.json
  • example/resources/langs/json/ru-RU.json
  • example/resources/langs/json/ru.json
  • example/resources/langs/xml/ar-DZ.xml
  • example/resources/langs/xml/de-DE.xml
  • example/resources/langs/xml/en-US.xml
  • example/resources/langs/xml/langs.xml
  • example/resources/langs/xml/ru-RU.xml
  • example/resources/langs/yml/ar-DZ.yaml
  • example/resources/langs/yml/de-DE.yaml
  • example/resources/langs/yml/en-US.yaml
  • example/resources/langs/yml/langs.yaml
  • example/resources/langs/yml/ru-RU.yaml
  • example/test/widget_test.dart
  • example/v3_vs_v4_comparison.md
  • i18n/ar/ar.json
  • i18n/ar_en/ar.json
  • i18n/ar_en/en.json
  • lib/easy_localization.dart
  • lib/src/asset_loader.dart
  • lib/src/compatibility.dart
  • lib/src/easy_localization_app.dart
  • lib/src/easy_localization_controller.dart
  • lib/src/easy_localization_storage_interface.dart
  • lib/src/exceptions.dart
  • lib/src/localization.dart
  • lib/src/plural_rules.dart
  • lib/src/public_ext.dart
  • lib/src/resolver.dart
  • lib/src/translations.dart
  • lib/src/widgets.dart
  • packages/easy_logger/pubspec.yaml
  • pubspec.yaml
  • test/compatibility_test.dart
  • test/easy_localization_context_test.dart
  • test/easy_localization_ctl_test.dart
  • test/easy_localization_test.dart
  • test/easy_localization_widget_test.dart
  • test/optimized_asset_loader_test.dart
  • test/resolver_test.dart
  • test/root_bundle_asset_loader_test.dart
  • test/utils/test_asset_loaders.dart

Walkthrough

Refactors asset-loading to named-parameter locale loading, injects AssetLoader and optional storage into initialization, rewires controller/provider to use injected dependencies, adds caching/storage/compatibility layers, updates plural/lookup behavior and exceptions, migrates test infrastructure, and updates docs and release metadata.

Changes

Localization v4 migration

Layer / File(s) Summary
AssetLoader contract, storage abstraction, and exceptions
lib/src/asset_loader.dart, lib/src/easy_localization_storage_interface.dart, lib/src/exceptions.dart
AssetLoader refactored from stateless load(String path, Locale locale)? to stateful base with optional path/supportedLocales and named-parameter load({Locale? locale}) returning non-null maps. RootBundleAssetLoader requires constructor path, removes path from load(), handles JSON decoding with static caching and clearCache(). CachedAssetLoader maintains per-locale translation cache; OptimizedAssetLoader validates locale before delegating. IEasyLocalizationStorage interface with SharedPreferencesStorage (lazy init, requires pre-init checks) and InMemoryStorage (map-backed) implementations. TranslationLoadException added for loader errors.
Initialization API and controller dependency injection
lib/src/easy_localization_app.dart, lib/src/easy_localization_controller.dart, lib/easy_localization.dart
EasyLocalization.ensureInitialized({AssetLoader, IEasyLocalizationStorage?}) replaces parameterless static method, forwards both to controller. Widget removes path, supportedLocales, and assetLoader fields; exposes only fallbackLocale, startLocale, useOnlyLangCode, useFallbackTranslations, saveLocale (defaulting to false), and errorWidget. Controller's initEasyLocation(AssetLoader, {IEasyLocalizationStorage?}) initializes storage, reads persisted locale, loads supportedLocales from loader. loadTranslationData delegates to assetLoader.load(locale:), throws TranslationLoadException. Controller static supportedLocales getter; provider/delegate use it instead of widget field. Locale persistence via storage only when saveLocale enabled.
Translation resolution, plural rules, and rendering
lib/src/localization.dart, lib/src/translations.dart, lib/src/public_ext.dart, lib/src/resolver.dart
Plural _pluralRule() always returns non-null function; plural() no longer has unreachable default case. Localization.exists() checks both primary and fallback translations. Translations.get() uses early returns for non-nested keys; isNestedKey() requires non-null translations; @visibleForTesting rawData getter added. New resolver.dart module provides resolve(), resolveNested(), resolveWithFallback() for key lookup with optional caching; resolveLinks() for link substitution with modifier support and onWarning callback; replaceArgs() and replaceNamedArgs() for placeholder substitution; keyExists() for existence checking. Text extension methods pass textScaler to Text() instead of deprecated textScaleFactor.
Compatibility layer and exports
lib/src/compatibility.dart, lib/easy_localization.dart
EasyLocalizationCompat extension adds withConfig() (constructs v4 EasyLocalization widget, logs warnings for deprecated v3 params) and migrateAssetLoader() (converts v3 AssetLoaderType to v4 AssetLoader, throws for unimplemented types). Public exports expanded for storage and compatibility modules; intl re-export narrowed to Intl, DateFormat, NumberFormat.
Test infrastructure and asset loader tests
test/utils/test_asset_loaders.dart, test/root_bundle_asset_loader_test.dart, test/optimized_asset_loader_test.dart, test/compatibility_test.dart, test/resolver_test.dart
Test asset loaders updated: ImmutableAssetLoader added; ImmutableJsonAssetLoader and JsonAssetLoader accept List<Locale> in constructor, load({Locale?}) override. New tests: root_bundle_asset_loader_test validates getLocalePath() and null-locale throws; optimized_asset_loader_test and CachedAssetLoader tests verify cache behavior; compatibility_test validates v3→v4 migration helpers; resolver_test comprehensive coverage of resolution, link substitution with modifiers, placeholder replacement, and existence checking.
Widget and controller integration tests
test/easy_localization_widget_test.dart, test/easy_localization_context_test.dart, test/easy_localization_ctl_test.dart, test/easy_localization_test.dart
Widget tests refactored to global ensureInitialized() with RootBundleAssetLoader(path: 'i18n/'), removing widget-level path/supportedLocales. Locale-change tests use startLocale; _getFallbackLocale() tests scope initialization with addTearDown to restore global state. Large SharedPreferences-dependent test groups commented out. Context tests simplified to validate supportedLocales and locale via global init. New controller unit tests verify selectLocaleFrom() behavior. Asset loader and persistence-dependent tests refactored to use initEasyLocation(assetLoader, storage).
Documentation, examples, and release metadata
README.md, CHANGELOG.md, example/v3_vs_v4_comparison.md, example/lib/main.dart, example/lib/lang_view.dart, pubspec.yaml, packages/easy_logger/pubspec.yaml, .github/workflows/playwright.yml, example/pubspec.yaml
README updated with parameterized ensureInitialized(assetLoader:) code samples, storage configuration (SharedPreferencesStorage, InMemoryStorage), and extended context properties list. CHANGELOG documents v4.0.0-dev breaking changes and migration notes. New v3_vs_v4_comparison.md contrasts init, custom loaders, usage patterns, and provides API changes summary. Example main.dart calls ensureInitialized(assetLoader: RootBundleAssetLoader(...)), narrows widget supportedLocales, uses useMaterial3: true. Example lang_view.dart benchmarks locale switches with Stopwatch, shows elapsed time in SnackBar, uses theme colors. Root pubspec.yaml bumps version to 4.0.0-dev.0, Dart SDK to >=2.17.0, intl to >=0.19.0, adds benchmark_harness dev dep, declares i18n asset dirs. Logger pubspec updates SDK to >=3.0.0. Playwright GitHub Actions workflow added. Example pubspec updates SDK to >=3.1.0 and assets. CMakeLists.txt uses dynamic platform variable; .gitignore entry removed; IDE files (.iml, .metadata) deleted. Example widget test disabled (commented out).

Sequence Diagram(s)

sequenceDiagram
  participant Main
  participant ELApp as EasyLocalization Widget
  participant CTL as EasyLocalizationController
  participant Storage as IEasyLocalizationStorage
  participant Loader as AssetLoader
  Main->>ELApp: ensureInitialized(assetLoader, storage)
  ELApp->>CTL: initEasyLocation(assetLoader, storage)
  CTL->>Storage: init()
  CTL->>Storage: getValue('locale')
  Storage-->>CTL: saved locale or null
  CTL->>Loader: load({locale})
  Loader-->>CTL: translations Map
  CTL->>Storage: setValue('locale', ...)
  CTL-->>ELApp: supportedLocales ready
  ELApp->>Main: initialized
  Note over ELApp: setLocale() uses injected loader & storage
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • Widget using Plural unable to be tested #745 — The refactoring directly addresses the root cause by introducing required EasyLocalization.ensureInitialized(assetLoader:) that properly initializes Localization._locale before any widgets use plural(), whereas the old widget-based API did not guarantee proper locale initialization.

Possibly related PRs

Poem

🐰 I've refactored the loaders, made storage plug in place,
Built dependency injection with async/await grace.
Controllers now take what they need, no guessing games,
Tests have been migrated—compatibility names!
From v3 to v4, the migration's complete,
With docs, examples, and tests all so neat! 🐇

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch V4

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🔭 Outside diff range comments (1)
example/lib/lang_view.dart (1)

Line range hint 39-43: Avoid hardcoded indices for locale selection.

Using hardcoded indices (context.supportedLocales[1]) is fragile and could break if the locale order changes.

Consider using locale codes instead:

- locale: context.supportedLocales[1] //BuildContext extension method
+ locale: context.supportedLocales.firstWhere(
+   (l) => l.languageCode == 'ar',
+   orElse: () => context.supportedLocales.first,
+ )
🧹 Nitpick comments (16)
lib/src/easy_localization_controller.dart (4)

3-3: Remove or clarify commented-out import
The newly added imports for path utilities, asset loader, and storage interface align with the refactor. However, the commented-out SharedPreferences import may cause confusion if left unused. Consider removing it to keep the codebase clean.

Also applies to: 8-8, 11-13


18-19: Revisit mutability of _storage
If the storage implementation isn’t meant to change at runtime, consider making _storage a late final to prevent accidental reassignment. This clarifies intent and may reduce the chance of concurrency issues.


154-158: Remove lingering SharedPreferences references
The commented-out code suggests a previous approach to locale persistence. Since storage is now handled via _storage, removing these lines can reduce clutter and confusion.


189-240: Clarify asset-based loading for cross-platform usage

  • Reading locales from a directory via dart:io is incompatible with platforms like web. Ensure that this path-based approach is either gated by platform checks or supplemented with a fallback.
  • Throwing io.PathExistsException might be misleading; a custom exception or a more descriptive message can improve debugging.
lib/src/easy_localization_storage_interface.dart (1)

1-19: Add typed getters to avoid runtime type errors
Using a generic method getValue<T>() is flexible but may introduce runtime type mismatches. Consider providing typed methods (e.g., getString()) or robust runtime checks. Also ensure close() is reliably invoked to release resources (if needed).

test/root_bundle_asset_loader_test.dart (1)

5-6: Remove TODO comment.

The comment "Replace with your package and file name" should be removed as it's no longer needed.

test/easy_localization_ctl_test.dart (1)

7-15: Consider using a mock logger instead of print override.

While the print override works, using a mock logger would be more maintainable and allow for better verification of log messages.

Consider this approach:

class MockLogger {
  final List<String> logs = [];
  void debug(String message) => logs.add(message);
}

final mockLogger = MockLogger();
// Inject mockLogger into EasyLocalizationController
example/lib/lang_view.dart (1)

101-105: Add semantic labels for accessibility.

The language selection tiles should have semantic labels for better accessibility.

Add semantics to the ListTile:

 child: ListTile(
     dense: true,
+    semanticLabel: 'Select $title language',
     title: Text(
       title,
     ),
example/lib/main.dart (2)

26-35: Consider removing commented-out configuration options.

The commented options for fallbackLocale and startLocale should either be implemented or removed to maintain clean code.


41-41: Consider removing debug print statement.

The debug print statement for supported locales should be removed before production deployment.

lib/src/easy_localization_app.dart (1)

104-109: Remove commented-out code.

The commented-out properties should be removed as they're now handled by the controller.

test/easy_localization_context_test.dart (1)

43-44: Consider adding error handling test cases for asset loader initialization.

The initialization of EasyLocalization with RootBundleAssetLoader should include test cases for error scenarios, such as invalid paths or missing assets.

 await EasyLocalization.ensureInitialized(
-      assetLoader: const RootBundleAssetLoader('i18n'));
+      assetLoader: const RootBundleAssetLoader('i18n')).catchError((error) {
+        expect(error, isA<AssetLoaderException>());
+      });
test/easy_localization_test.dart (1)

75-85: Remove commented code to improve readability.

The commented parameters are no longer needed with the new initialization pattern.

 var r1 = 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 JsonAssetLoader()
test/easy_localization_widget_test.dart (1)

71-78: Consider enabling logger levels for debugging.

The commented logger levels could be useful for debugging. Consider keeping them enabled in test environment.

 TestWidgetsFlutterBinding.ensureInitialized();
 SharedPreferences.setMockInitialValues({});
-  // EasyLocalization.logger.enableLevels = <LevelMessages>[
-  //   LevelMessages.error,
-  //   LevelMessages.warning,
-  // ];
+  EasyLocalization.logger.enableLevels = <LevelMessages>[
+    LevelMessages.error,
+    LevelMessages.warning,
+  ];
i18n/ar/ar.json (1)

1-11: Add metadata and more comprehensive translations.

Consider enhancing the JSON file with:

  1. Metadata about the language and region
  2. More comprehensive test cases
  3. Documentation for pluralization rules
 {
+  "_metadata": {
+    "language": "Arabic",
+    "country": "General",
+    "pluralization_rules": "Arabic has six plural forms"
+  },
   "test": "اختبار",
   "day": {
     "zero": "{} يوم",
     "one": "{} يوم",
     "two": "{} أيام",
     "few": "{} أيام",
     "many": "{} يوم",
     "other": "{} يوم"
   }
 }
example/pubspec.yaml (1)

Line range hint 31-34: Address the commented loader package dependency issue.

There's a FIXME comment regarding the easy_localization_loader package dependencies issue that needs to be resolved.

Would you like me to help investigate and resolve the dependency issue with the easy_localization_loader package? I can help create an issue to track this.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 820c9d8 and b5a12c3.

⛔ Files ignored due to path filters (1)
  • example/lib/generated/codegen_loader.g.dart is excluded by !**/generated/**
📒 Files selected for processing (21)
  • bin/generate.dart (1 hunks)
  • example/lib/lang_view.dart (3 hunks)
  • example/lib/main.dart (3 hunks)
  • example/linux/flutter/CMakeLists.txt (1 hunks)
  • example/pubspec.yaml (2 hunks)
  • example/test/widget_test.dart (1 hunks)
  • i18n/ar/ar.json (1 hunks)
  • i18n/ar_en/ar.json (1 hunks)
  • i18n/ar_en/en.json (1 hunks)
  • lib/easy_localization.dart (1 hunks)
  • lib/src/asset_loader.dart (1 hunks)
  • lib/src/easy_localization_app.dart (7 hunks)
  • lib/src/easy_localization_controller.dart (4 hunks)
  • lib/src/easy_localization_storage_interface.dart (1 hunks)
  • pubspec.yaml (1 hunks)
  • test/easy_localization_context_test.dart (1 hunks)
  • test/easy_localization_ctl_test.dart (1 hunks)
  • test/easy_localization_test.dart (7 hunks)
  • test/easy_localization_widget_test.dart (9 hunks)
  • test/root_bundle_asset_loader_test.dart (1 hunks)
  • test/utils/test_asset_loaders.dart (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • example/test/widget_test.dart
  • i18n/ar_en/en.json
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (25)
lib/src/easy_localization_controller.dart (5)

1-2: Ensure platform compatibility when using dart:io
While this import is necessary for reading directories on platforms that support I/O, be aware that web builds don’t fully support dart:io. If web compatibility is intended, consider a conditional import or alternative approach.


26-29: Initialize late fields thoroughly
Ensure _assetLoader and _locale are always assigned during initialization. If the controller initialization is skipped in certain conditions (e.g., testing), referencing these fields prematurely can throw runtime errors.


102-117: Validate fallback translation flow
When loading fallback translations, the code merges base and fallback data. This approach appears correct, but keep in mind that _listtranslationData is purged after its first use, forcing subsequent calls to rely solely on _assetLoader. Verify that this matches your intended behavior for multi-locale or repeated fallback scenarios.


162-185: Confirm one-time vs. repeated initEasyLocation calls
This static method sets _storage and _assetLoader globally. If initEasyLocation can be invoked multiple times, ensure that repeated calls won’t overwrite existing values or inadvertently reset partially loaded data.


241-241: Locale deletion confirmed
Removing the saved locale updates both the in-memory state and _storage. If an application flow requires resetting to a fallback or re-initializing after deletion, consider prompting re-initialization or reloading to avoid unexpected locale states.

Also applies to: 244-244

lib/easy_localization.dart (1)

7-7: Export interface for custom storage
Exporting easy_localization_storage_interface.dart allows external code to implement custom storage strategies, extending the library’s flexibility without tying users to a single persistence layer.

test/easy_localization_ctl_test.dart (1)

50-51: Add test cases for core functionality.

The TODO comment indicates missing test cases. Based on the PR objectives, tests should be added for:

  • Asset loading initialization
  • Storage interface integration
  • Cached asset loader functionality

Let me help verify what needs to be tested:

✅ Verification successful

Add test cases for core functionality

The review comment is accurate. The codebase shows several untested core components:

  • Asset Loaders:
    • CodegenLoader
    • ImmutableAssetLoader
    • ImmutableJsonAssetLoader
    • JsonAssetLoader
  • Storage interface implementation and integration
  • EasyLocalizationController functionality (currently has commented-out test cases)

The existing test coverage only covers RootBundleAssetLoader and basic Localization.load() scenarios.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find core functionality that needs test coverage
echo "Searching for core functionality..."
rg -t dart "class.*Storage|class.*AssetLoader|class.*Controller" --no-filename

# Find existing test coverage
echo -e "\nSearching for existing tests..."
rg -t dart "^.*test.*Storage|^.*test.*AssetLoader|^.*test.*Controller" --no-filename

Length of output: 840


Script:

#!/bin/bash
# Find all test files
echo "All test files:"
fd -e dart "_test.dart$"

echo -e "\nController implementation:"
ast-grep --pattern 'class EasyLocalizationController {
  $$$
}'

echo -e "\nExisting test cases (broader search):"
rg -t dart "test.*\b(Storage|AssetLoader|Controller|load|save|init)" -A 2

Length of output: 2722

test/utils/test_asset_loaders.dart (3)

5-14: LGTM! Well-structured test utility class.

The ImmutableAssetLoader provides a clean, minimal implementation suitable for testing scenarios.


17-25: LGTM! Constructor and method signature updates align with new interface.

The changes properly implement the new AssetLoader interface while maintaining the test utility's immutable nature.


Line range hint 29-103: LGTM! Comprehensive test data with proper null safety.

The changes properly implement the new AssetLoader interface while maintaining a robust test data structure. The null-safe access to locale?.languageCode is a good practice.

example/lib/main.dart (2)

Line range hint 11-24: LGTM! Proper initialization setup with asset loader configuration.

The initialization is correctly configured with the new ensureInitialized method, providing a clear path for asset loading.


47-47: LGTM! Material 3 adoption.

Good practice to adopt Material 3 for modern UI components and features.

lib/src/easy_localization_app.dart (3)

65-67: Verify impact of changing saveLocale default to false.

This change might affect existing applications that rely on locale persistence. Consider documenting this breaking change in migration guide.


82-87: LGTM! Well-structured initialization method.

The ensureInitialized method properly handles both assetLoader and storage initialization.


171-172: LGTM! Proper centralization of locale management.

Good practice to centralize supported locales in the controller and validate against it.

Also applies to: 194-194

bin/generate.dart (1)

247-247: LGTM! Method signature properly updated for null safety.

The load method correctly implements the new AssetLoader interface with proper null safety.

test/easy_localization_context_test.dart (2)

66-236: 🛠️ Refactor suggestion

Consider keeping essential test cases from commented code.

While removing SharedPreferences dependency is good, some of the commented test cases cover important functionality like fallback locale and device locale handling. Consider preserving these tests with the new storage interface.


60-61: Verify test coverage for supported locales.

The test only checks the length of supported locales but not their specific values. Consider adding more detailed assertions.

 expect(_context.supportedLocales.length, 4);
+expect(_context.supportedLocales, containsAll([
+  const Locale("ar", "DZ"),
+  const Locale("en"),
+  const Locale("ar"),
+  const Locale("en", "US")
+]));
✅ Verification successful

The review suggestion is accurate and should be implemented

The suggested assertion precisely matches the actual supported locales found in the i18n directory:

  • ar-DZ
  • ar
  • en
  • en-US
🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# First, let's look at the test file content
cat test/easy_localization_context_test.dart

Length of output: 8339


Script:

#!/bin/bash
# Look for locale definitions in the source code
rg -l "supportedLocales" --type dart

# Also check for any i18n configuration files
fd "i18n"

Length of output: 414


Script:

#!/bin/bash
# Check i18n directory content
ls -la i18n/

# Look at the initialization code in the main implementation file
cat lib/src/easy_localization_app.dart

Length of output: 7980

test/easy_localization_test.dart (1)

26-48: LGTM! Good addition of asset loader test cases.

The new test group for asset loader properly verifies error cases for path and supported locales.

i18n/ar_en/ar.json (1)

1-11: Verify Arabic pluralization rules and placeholder consistency.

The JSON structure looks good, but there are a few points to consider:

  1. The placeholder {} is consistently used across all plural forms
  2. The plural forms follow Arabic grammar rules with appropriate forms for zero, one, two, few, many, and other cases

Let's validate the JSON structure and encoding:

✅ Verification successful

Arabic pluralization rules and placeholders are correctly implemented

The plural forms and placeholders are consistent across all Arabic translation files and follow the correct Arabic grammar rules.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify JSON validity and UTF-8 encoding
file="i18n/ar_en/ar.json"
if [ -f "$file" ]; then
    # Check JSON syntax
    jq '.' "$file" > /dev/null
    
    # Verify UTF-8 encoding
    file -i "$file" | grep -q "charset=utf-8"
    
    # Print the normalized content for visual inspection
    jq -r '.' "$file"
fi

Length of output: 430


Script:

#!/bin/bash
# Check for other Arabic translation files and references
echo "Looking for other Arabic translation files:"
fd -e json . i18n | grep -i "ar"

echo -e "\nChecking for files referencing Arabic translations:"
rg -l "ar\.json" --type ts --type js

Length of output: 519


Script:

#!/bin/bash
# Compare plural rules across Arabic translation files
echo "=== Standard Arabic (ar.json) ==="
jq -r '.day | select(.)' i18n/ar.json 2>/dev/null || echo "No 'day' key found"

echo -e "\n=== Algerian Arabic (ar-DZ.json) ==="
jq -r '.day | select(.)' i18n/ar-DZ.json 2>/dev/null || echo "No 'day' key found"

echo -e "\n=== Arabic in ar/ directory ==="
jq -r '.day | select(.)' i18n/ar/ar.json 2>/dev/null || echo "No 'day' key found"

echo -e "\n=== Arabic in ar_en/ directory (current file) ==="
jq -r '.day | select(.)' i18n/ar_en/ar.json 2>/dev/null || echo "No 'day' key found"

Length of output: 1023

pubspec.yaml (3)

8-8: Version bump indicates breaking changes.

The version change to 4.0.0-dev.0 correctly follows semantic versioning for breaking changes, as indicated in the PR objectives regarding initialization changes.


51-55: Asset paths align with new localization structure.

The flutter assets section correctly includes the new i18n directory structure, which supports the improved asset loading mechanism mentioned in the PR objectives.


33-33: Verify compatibility impact of SDK constraint.

The SDK constraint has been updated to require Dart >=3.1.0. This is a significant change that might affect existing users.

✅ Verification successful

SDK constraint change aligns with existing Dart 3 support

The package already supports Dart 3 since version 3.0.2 as documented in the changelog, making this constraint change a formalization of existing compatibility.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check pub points and SDK compatibility
curl -s "https://pub.dev/api/packages/easy_localization" | \
jq -r '.versions[] | select(.version | contains("3.")) | .pubspec.environment.sdk' | \
sort -V | tail -n 1

Length of output: 179


Script:

#!/bin/bash
# Check for migration guides and changelog entries
echo "=== Checking for CHANGELOG.md ==="
if [ -f CHANGELOG.md ]; then
    head -n 20 CHANGELOG.md
fi

echo -e "\n=== Checking for migration guides ==="
fd -t f -e md migration
fd -t f -e md guide

echo -e "\n=== Checking for Dart 3.0 related changes ==="
rg -i "dart 3" -A 2 -B 2

Length of output: 942

example/linux/flutter/CMakeLists.txt (1)

85-85: LGTM! Platform-agnostic build configuration.

The change to use ${FLUTTER_TARGET_PLATFORM} makes the build configuration more flexible and platform-agnostic.

example/pubspec.yaml (1)

52-54: Resource paths support multiple localization formats.

The addition of paths for JSON, YML, and XML formats aligns with the improved asset loading mechanism. However, ensure that the corresponding loader implementations are available and tested.

Consider documenting the supported formats and their usage in the migration guide.

Comment thread lib/src/easy_localization_controller.dart Outdated
Comment thread lib/src/easy_localization_controller.dart
Comment thread lib/src/easy_localization_controller.dart Outdated
Comment thread lib/src/asset_loader.dart
Comment on lines +7 to +32
/// 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});

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:

Comment thread lib/src/asset_loader.dart
Comment on lines +36 to 66
/// 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';
}

///
/// 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));
}

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 on lines +1 to +31
import 'package:easy_localization/src/asset_loader.dart';

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
// Replace with your package and file name

void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('RootBundleAssetLoader', () {
const path = 'i18n'; // Replace with the actual asset path
const locale = Locale('en');
late RootBundleAssetLoader assetLoader;

setUp(() async {
assetLoader = const RootBundleAssetLoader(path);
await assetLoader.load(locale: locale);
});

test('getLocalePath returns the correct path', () async {
const expectedPath = '$path/en.json';
var result = assetLoader.getLocalePath(locale);

expect(result, expectedPath);
});

test('load throws an error when the locale is null', () {
assetLoader = const RootBundleAssetLoader(path);
expect(() => assetLoader.load(locale: null), throwsArgumentError);
});
});
}

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

Enhance test coverage with more test cases.

The test file needs additional test cases to cover error scenarios and actual asset loading.

Consider adding these test cases:

test('load throws AssetLoadException when file not found', () {
  assetLoader = const RootBundleAssetLoader('invalid/path');
  expect(
    () => assetLoader.load(locale: locale),
    throwsA(isA<AssetLoadException>()),
  );
});

test('load throws AssetLoadException for invalid JSON', () {
  // Mock rootBundle.loadString to return invalid JSON
  expect(
    () => assetLoader.load(locale: locale),
    throwsA(isA<AssetLoadException>()),
  );
});

Comment thread test/easy_localization_widget_test.dart
@aissat aissat closed this Jan 26, 2025
@aissat aissat reopened this Jan 26, 2025
@MatrixRave

Copy link
Copy Markdown

Seems like a valid improvement to me. Would be nice to see this getting approved or any recognition.

@aissat aissat requested a review from Copilot July 24, 2025 21:59

This comment was marked as outdated.

@aissat aissat requested a review from Copilot August 11, 2025 20:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements a major architectural overhaul to migrate EasyLocalization from v3.x to v4.x, focusing on improved initialization, asset loading, and storage abstraction. The changes introduce a new initialization pattern that separates asset loading from widget configuration and provides better performance through optimized loading strategies.

Key changes include:

  • Introduction of ensureInitialized method to handle asset loading and storage setup before widget creation
  • Removal of supportedLocales and path parameters from the EasyLocalization widget constructor
  • New storage abstraction layer through IEasyLocalizationStorage interface
  • Updated asset loader architecture requiring path specification during initialization

Reviewed Changes

Copilot reviewed 20 out of 40 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
test/utils/test_asset_loaders.dart Updates test asset loaders to match new AssetLoader interface with supportedLocales parameter
test/root_bundle_asset_loader_test.dart Adds comprehensive tests for RootBundleAssetLoader with new path-based initialization
test/easy_localization_widget_test.dart Updates widget tests to use new initialization pattern and removes deprecated parameters
test/easy_localization_test.dart Refactors localization tests to use new controller initialization approach
test/easy_localization_ctl_test.dart Adds new controller-specific tests for locale selection logic
test/easy_localization_context_test.dart Updates context extension tests for new initialization flow
pubspec.yaml Bumps version to 4.0.0-dev.0, updates SDK requirement, and adds comprehensive topic tags
lib/src/easy_localization_storage_interface.dart Introduces new storage abstraction interface
lib/src/easy_localization_controller.dart Major refactor implementing new initialization and storage patterns
lib/src/easy_localization_app.dart Updates main widget to remove deprecated parameters and use new initialization
lib/src/asset_loader.dart Modernizes AssetLoader with path requirement and improved error handling
lib/easy_localization.dart Exports new storage interface
i18n/ files Adds sample localization files for testing
example/ files Updates example app to demonstrate new initialization pattern

/// Retrieves the value associated with the given [key].
///
/// Returns the value of type [T].
T getValue<T>(String key);

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 getValue method should return Future<T?> instead of T to handle asynchronous storage operations and potential null values consistently with other methods in the interface.

Suggested change
T getValue<T>(String key);
Future<T?> getValue<T>(String key);

Copilot uses AI. Check for mistakes.
storage?.init();

// Get the saved locale from the storage
final strLocale = storage?.getValue<String>('locale');

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 getValue method is called synchronously but should likely be awaited since storage operations are typically asynchronous. This could lead to incorrect behavior.

Suggested change
final strLocale = storage?.getValue<String>('locale');
final strLocale = await storage?.getValue<String>('locale');

Copilot uses AI. Check for mistakes.
} else {
throw io.PathExistsException;
}
}

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.

Using io.Directory for asset loading is incorrect. Flutter assets are not file system directories but are embedded in the app bundle. This will cause runtime errors in production builds.

Suggested change
}
// Set the supported locales directly from loader.supportedLocales
if (loader.supportedLocales == null) {
throw Exception('supportedLocales must be provided when using asset loading in Flutter. Listing asset directories is not supported.');
}
_supportedLocales = loader.supportedLocales!;

Copilot uses AI. Check for mistakes.
.toLocale(separator: '-'))
.toList();
} else {
throw io.PathExistsException;

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.

PathExistsException is not the appropriate exception type for a missing directory. Consider using PathNotFoundException or a custom exception.

Suggested change
throw io.PathExistsException;
throw io.PathNotFoundException(loader.path!);

Copilot uses AI. Check for mistakes.
Comment thread lib/src/asset_loader.dart
/// [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.
Comment thread example/lib/main.dart
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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 40 changed files in this pull request and generated 14 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 31 to 34
final Function(FlutterError e) onLoadError;
// ignore: prefer_typing_uninitialized_variables
final assetLoader;
final String path;
final bool useFallbackTranslations;
final bool saveLocale;
final bool useOnlyLangCode;
Comment thread example/lib/main.dart
child: MyApp(),
// fallbackLocale: Locale('en', 'US'),
// startLocale: Locale('ar', 'DZ'),
supportedLocales: [Locale('en', 'US'), Locale('ar', 'DZ')],
Comment thread example/lib/main.dart
// useOnlyLangCode: true,

await EasyLocalization.ensureInitialized(
assetLoader: RootBundleAssetLoader(path: 'resources/langs/json'),
Comment on lines +82 to +86
static Future<void> ensureInitialized({
AssetLoader assetLoader = const RootBundleAssetLoader('resources/langs'),
IEasyLocalizationStorage? storage,
}) async =>
await EasyLocalizationController.initEasyLocation(assetLoader,
Comment on lines +127 to +130
try {
if (_listtranslationData != null) {
var index = _supportedLocales.indexOf(locale);
translationData = _listtranslationData![index];
Comment thread lib/src/asset_loader.dart
Comment on lines +49 to 51
String getLocalePath(Locale locale) {
return '$path/${locale.toStringWithSeparator(separator: "-")}.json';
}
Comment on lines 50 to 52
/// Save locale in device storage.
/// @Default value true
final bool saveLocale;
Comment on lines 14 to +18
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 +124 to +128
Future<Map<String, dynamic>> loadTranslationData(Locale locale) async {
late Map<String, dynamic>? data;
late final Map<String, dynamic> translationData;

if (useOnlyLangCode) {
data = await assetLoader.load(path, Locale(locale.languageCode));
} else {
data = await assetLoader.load(path, locale);
try {
if (_listtranslationData != null) {
Comment on lines +366 to +370
// group('SharedPreferences SavedLocale NULL', () {
// setUp(() {
// SharedPreferences.setMockInitialValues({
// 'locale': '',
// });
aissat added 7 commits June 9, 2026 20:54
Introduced CachedAssetLoader for base functionality and updated
RootBundleAssetLoader to include internal translation caching. Added
OptimizedAssetLoader as a wrapper for backward compatibility.
- Optimize translation lookup and caching
- Add `TranslationLoadException` for better error handling
- Update `TextTranslateExtension` to use `TextScaler`
- Refactor `EasyLocalizationController` for cleaner initialization
- Improve plural rule evaluation fallback
- Upgrade intl to version 0.19.0+
- Add benchmark_harness to dev_dependencies
- Standardize SDK version quote style
Included a stopwatch to measure the locale switching time and displayed
the
result via a SnackBar to provide immediate performance feedback.
- Add constructor to CachedAssetLoader
- Prevent overwriting existing keys when merging fallback translations
- Log TranslationLoadException details
- Expose raw data in Translations for testing purposes
Updated test suite to use named arguments for RootBundleAssetLoader and
initialized controllers within setUpAll to ensure correct state.
Update IEasyLocalizationStorage to use Future for all operations to
support async-first persistence layers like SharedPreferences. Added
concrete SharedPreferences and InMemory implementations.
- Refactored `EasyLocalization.ensureInitialized` to accept
  `assetLoader`
  and optional `storage`.
- Added `IEasyLocalizationStorage` interface with
  `SharedPreferencesStorage`
  and `InMemoryStorage` implementations.
- Updated README examples and widget properties to reflect the new
  initialization flow.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
lib/src/easy_localization_app.dart (2)

188-194: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

assert for locale validation only runs in debug mode.

Line 191 uses assert to validate that the locale is supported. In release builds, this check is stripped, allowing unsupported locales to be set silently. Consider using an explicit check that throws in all modes:

Suggested fix
 Future<void> setLocale(Locale locale) async {
   // Check old locale
   if (locale != _localeState.locale) {
-    assert(EasyLocalizationController.supportedLocales.contains(locale));
+    if (!EasyLocalizationController.supportedLocales.contains(locale)) {
+      throw ArgumentError('Locale $locale is not in supportedLocales: ${EasyLocalizationController.supportedLocales}');
+    }
     await _localeState.setLocale(locale);
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/easy_localization_app.dart` around lines 188 - 194, The setLocale
method uses assert(EasyLocalizationController.supportedLocales.contains(locale))
which is stripped in release builds, so replace the assert with an explicit
runtime check in Future<void> setLocale(Locale locale) that verifies
EasyLocalizationController.supportedLocales.contains(locale) and throws a clear
exception (e.g., ArgumentError or StateError) or returns an error before calling
_localeState.setLocale(locale); keep the existing early-return when locale ==
_localeState.locale and ensure the error message names the unsupported locale
and references EasyLocalizationController.supportedLocales for debugging.

50-52: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Documentation says saveLocale defaults to true, but actual default is false.

The doc comment on line 51 states "@Default value true", but the constructor on line 65 sets this.saveLocale = false. Update the documentation to match the implementation.

Fix documentation
   /// Save locale in device storage.
-  /// `@Default` value true
+  /// `@Default` value false
   final bool saveLocale;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/easy_localization_app.dart` around lines 50 - 52, The doc comment for
the field saveLocale is wrong (it says "`@Default` value true" while the
EasyLocalizationApp constructor sets this.saveLocale = false); update the
documentation on the saveLocale field to state the default is false (or mirror
the implementation) so the comment matches the EasyLocalizationApp constructor
and the saveLocale field description.
test/easy_localization_test.dart (2)

41-47: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Error-path tests can pass even when no error occurs.

These tests do not fail if loadTranslations() completes normally, and they assert thrown exceptions even though loadTranslations() reports loader failures through onLoadError instead of rethrowing.

Suggested test pattern update
-    test('is Assertion Error when path and supportedLocales null', () async {
-      try {
-        await r1.loadTranslations();
-      } on AssertionError catch (e) {
-        expect(e, isAssertionError);
-      }
-    });
+    test('calls onLoadError when loader config is invalid', () async {
+      FlutterError? captured;
+      final c = EasyLocalizationController(
+        forceLocale: const Locale('en'),
+        useOnlyLangCode: true,
+        useFallbackTranslations: false,
+        saveLocale: false,
+        onLoadError: (e) => captured = e,
+      );
+      await c.loadTranslations();
+      expect(captured, isNotNull);
+    });

Also applies to: 64-70

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/easy_localization_test.dart` around lines 41 - 47, The tests expecting
an AssertionError from await r1.loadTranslations() are invalid because
loadTranslations reports errors via onLoadError rather than throwing; update the
tests (both the "is Assertion Error when path and supportedLocales null" and the
similar one at 64-70) to register an onLoadError handler and assert that it was
invoked (e.g., set a boolean or use a mock/callback) when calling
r1.loadTranslations(), and remove any try/catch that expects an AssertionError
or replace it with an expectation that the future completes and the onLoadError
callback was called; reference r1.loadTranslations and the onLoadError handler
in your changes.

290-315: ⚠️ Potential issue | 🟠 Major

Move initEasyLocation into setUpAll so loadTranslations() doesn’t depend on hook order (group('tr') / group('plural'))
In test/easy_localization_test.dart, setUpAll calls r.loadTranslations() + Localization.load(...), but initEasyLocation(...) (which sets EasyLocalizationController’s static late _assetLoader) runs only in setUp. setUpAll runs before setUp, so this is order-dependent and can fail when running the group in isolation.

Suggested fix shape
-      setUp(() async {
-        EasyLocalization.logger.enableLevels = LevelMessages.values.toList();
-        await EasyLocalizationController.initEasyLocation(
-            const JsonAssetLoader([Locale('en'), Locale('fb')]));
-      });
+      setUpAll(() async {
+        EasyLocalization.logger.enableLevels = LevelMessages.values.toList();
+        await EasyLocalizationController.initEasyLocation(
+            const JsonAssetLoader([Locale('en'), Locale('fb')]));
+        await r.loadTranslations();
+        Localization.load(const Locale('en'),
+            translations: r.translations,
+            fallbackTranslations: r.fallbackTranslations);
+      });

-      setUpAll(() async {
-        await r.loadTranslations();
-        Localization.load(const Locale('en'),
-            translations: r.translations,
-            fallbackTranslations: r.fallbackTranslations);
-      });

Also apply the same restructuring to group('plural') (around lines 481-505).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/easy_localization_test.dart` around lines 290 - 315, The test fails due
to initEasyLocation(...) (which sets EasyLocalizationController's static late
_assetLoader) being called in setUp while r.loadTranslations() and
Localization.load(...) are invoked in setUpAll (which runs earlier); move the
initEasyLocation(...) call into the same setUpAll that calls
r.loadTranslations() and Localization.load(...) so the asset loader is
initialized before loadTranslations() runs (apply the same change inside both
group('tr') and group('plural')), keeping the EasyLocalization.logger setup as
needed and leaving r, loadTranslations, and Localization.load calls intact.
🧹 Nitpick comments (4)
pubspec.yaml (1)

42-42: Intl constraint update is unlikely to break Intl.defaultLocale / NumberFormat.currency

intl 0.19.0 didn’t introduce breaking public API changes for Intl.defaultLocale or NumberFormat.currency, so the updated constraint >=0.19.0 <0.21.0 should be API-compatible across 0.19.x0.20.x. Dropping 0.17.x/0.18.x can still affect dependency resolution for downstreams (they may need to upgrade to satisfy your new pub constraint), so confirm whether that restriction is intentional/required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pubspec.yaml` at line 42, The pubspec change tightened the intl constraint to
">=0.19.0 <0.21.0" which may force downstream consumers off 0.17/0.18; decide
whether that restriction is intentional and either revert or broaden the range.
If you intend to require >=0.19.0, add a short comment in pubspec.yaml or the PR
description explaining that Intl.defaultLocale and NumberFormat.currency
compatibility was verified for 0.19.x–0.20.x; otherwise change the dependency
line for intl back to include 0.17/0.18 (e.g., a wider lower bound) so
downstreams aren’t forced to upgrade. Ensure the dependency string referring to
intl in pubspec.yaml and any CI/pub upgrade notes reflect the chosen scope.
lib/src/asset_loader.dart (1)

121-121: 💤 Low value

Missing const constructor.

OptimizedAssetLoader constructor is not const unlike its parent RootBundleAssetLoader. If intentional (due to non-const initialization), document why; otherwise add const for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/asset_loader.dart` at line 121, The OptimizedAssetLoader constructor
should be made const to match its parent RootBundleAssetLoader unless there's
non-const initialization—update the constructor declaration
OptimizedAssetLoader({required String path}) : super(path: path); to be const
(i.e., add the const keyword) so instances can be compile-time constants; if
there is a deliberate reason it cannot be const, add a brief comment in the
class (near OptimizedAssetLoader) explaining why const was omitted.
lib/src/easy_localization_controller.dart (2)

126-138: ⚖️ Poor tradeoff

Concurrent setLocale calls are silently dropped.

The _loading guard on lines 127-128 causes concurrent locale change requests to be silently ignored. If a user rapidly switches locales, intermediate requests are lost without feedback. Consider queuing the latest request or returning a status indicating the call was skipped.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/easy_localization_controller.dart` around lines 126 - 138, The
setLocale(Locale l) method currently drops concurrent calls due to the _loading
boolean guard; change it to either (A) queue the latest requested locale by
adding a _pendingLocale field and, if _loading is true, assign _pendingLocale =
l and return (or await completion then apply pending), or (B) make setLocale
return a status/future indicating it was skipped by returning a bool/Result when
_loading is true. Implement the chosen approach inside setLocale and ensure you
still call loadTranslations(), notifyListeners(), EasyLocalization.logger(...)
and _saveLocale(_locale) for the applied locale, and clear/update _pendingLocale
and the _loading flag (or resolve awaiting callers) so no intermediate requests
are silently lost.

80-89: 💤 Low value

loadTranslationData wraps exceptions but loses stack trace context.

Line 87 creates a new TranslationLoadException but the original stack trace from line 83 is logged but not preserved in the exception. Consider including the stack trace in the exception for better debugging downstream.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/easy_localization_controller.dart` around lines 80 - 89, The catch
block in loadTranslationData logs the original stackTrace but throws a new
TranslationLoadException without preserving it; modify the error handling so the
original stackTrace is carried into the thrown exception—update the
TranslationLoadException type (e.g., add an optional StackTrace parameter or a
factory like TranslationLoadException.withTrace) and change the throw in
loadTranslationData to throw TranslationLoadException(locale, e, stackTrace) (or
use the new factory), keeping EasyLocalization.logger.error(...) as-is and
ensuring _assetLoader.load errors retain their original stack trace for
downstream debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/src/asset_loader.dart`:
- Around line 59-70: CachedAssetLoader.load() currently returns an empty Map on
cache miss which causes missing translations; change it so that when
!_translationCache.containsKey(locale) it loads translations from the actual
loader and caches them instead of returning {}. Locate the
CachedAssetLoader.load method and use the real loading mechanism (for example
call the underlying loader method or super.load if this class wraps/extends a
real loader) to obtain the Map<String, dynamic>, store it in
_translationCache[locale], and then return the loaded map; keep the existing
null-check for locale and the early return for cached entries.
- Around line 118-140: OptimizedAssetLoader is double-caching translations;
remove the redundant internal cache and rely on RootBundleAssetLoader's built-in
caching: delete the static Map<Locale, Map<String, dynamic>> _cache and the
clearCache() method, keep the constructor and the load({Locale? locale})
override but simplify it to only validate locale, optionally log a cache-hit by
probing super.load's behavior is unnecessary, then call and return await
super.load(locale: locale); this preserves behavior while eliminating duplicate
storage and references to OptimizedAssetLoader._cache.

In `@lib/src/easy_localization_controller.dart`:
- Around line 140-145: The early-return condition in _saveLocale is inverted:
change the guard so the method returns when saving is disabled OR storage is
unavailable (i.e., when saveLocale is false OR _storage is null). Update the
condition in Future<void> _saveLocale(Locale? locale) accordingly (referencing
_saveLocale, saveLocale, and _storage) so that await
_storage?.setValue('locale', ...) and EasyLocalization.logger(...) only run when
saving is enabled and storage exists.

In `@lib/src/easy_localization_storage_interface.dart`:
- Around line 30-42: The storage methods getValue, setValue, and removeValue
currently use the nullable _prefs with the safe-call operator and thus silently
no-op if init() wasn't called; update these methods (getValue, setValue,
removeValue) to guard against a null _prefs by either throwing a clear
StateError (e.g., "SharedPreferences not initialized; call init()") when _prefs
== null or by exposing an isInitialized getter and returning a controlled error
for callers — implement one approach consistently and ensure all three methods
check _prefs and fail fast instead of silently returning null/do-nothing.

In `@pubspec.yaml`:
- Line 33: The pubspec environment.sdk currently requires ">=3.1.0 <4.0.0" but
the codebase doesn’t use Dart 3.1+ language features; update the SDK constraint
to the lowest compatible version (e.g., ">=2.18.0 <4.0.0" or whatever minimum
your dependencies/tools require) in the pubspec.yaml environment.sdk field;
before committing, verify all package dependencies and CI/tooling (pubspec.lock,
analyzer/formatter versions, and any GitHub Actions or build scripts) support
the lower SDK and run dart analyze and dart pub get to ensure no SDK-specific
errors remain.

In `@test/easy_localization_widget_test.dart`:
- Around line 320-322: The tests call EasyLocalization.ensureInitialized with a
custom RootBundleAssetLoader(path: 'i18n/ar') but never restore
EasyLocalizationController's global state (_assetLoader, _supportedLocales,
_locale), making later tests order-dependent; after each test that switches to
'i18n/ar' (the calls at/around EasyLocalization.ensureInitialized(assetLoader:
const RootBundleAssetLoader(path: 'i18n/ar'))), add teardown code to restore the
global EasyLocalization state by calling EasyLocalization.ensureInitialized
again with the default loader and expected defaults (e.g.,
EasyLocalization.ensureInitialized(assetLoader: const RootBundleAssetLoader(),
supportedLocales: <your-defaults>, locale: yourDefaultLocale)) or wrap the
change in a try/finally so the finally block re-initializes EasyLocalization to
the original/default loader/locale to contain the change.

---

Outside diff comments:
In `@lib/src/easy_localization_app.dart`:
- Around line 188-194: The setLocale method uses
assert(EasyLocalizationController.supportedLocales.contains(locale)) which is
stripped in release builds, so replace the assert with an explicit runtime check
in Future<void> setLocale(Locale locale) that verifies
EasyLocalizationController.supportedLocales.contains(locale) and throws a clear
exception (e.g., ArgumentError or StateError) or returns an error before calling
_localeState.setLocale(locale); keep the existing early-return when locale ==
_localeState.locale and ensure the error message names the unsupported locale
and references EasyLocalizationController.supportedLocales for debugging.
- Around line 50-52: The doc comment for the field saveLocale is wrong (it says
"`@Default` value true" while the EasyLocalizationApp constructor sets
this.saveLocale = false); update the documentation on the saveLocale field to
state the default is false (or mirror the implementation) so the comment matches
the EasyLocalizationApp constructor and the saveLocale field description.

In `@test/easy_localization_test.dart`:
- Around line 41-47: The tests expecting an AssertionError from await
r1.loadTranslations() are invalid because loadTranslations reports errors via
onLoadError rather than throwing; update the tests (both the "is Assertion Error
when path and supportedLocales null" and the similar one at 64-70) to register
an onLoadError handler and assert that it was invoked (e.g., set a boolean or
use a mock/callback) when calling r1.loadTranslations(), and remove any
try/catch that expects an AssertionError or replace it with an expectation that
the future completes and the onLoadError callback was called; reference
r1.loadTranslations and the onLoadError handler in your changes.
- Around line 290-315: The test fails due to initEasyLocation(...) (which sets
EasyLocalizationController's static late _assetLoader) being called in setUp
while r.loadTranslations() and Localization.load(...) are invoked in setUpAll
(which runs earlier); move the initEasyLocation(...) call into the same setUpAll
that calls r.loadTranslations() and Localization.load(...) so the asset loader
is initialized before loadTranslations() runs (apply the same change inside both
group('tr') and group('plural')), keeping the EasyLocalization.logger setup as
needed and leaving r, loadTranslations, and Localization.load calls intact.

---

Nitpick comments:
In `@lib/src/asset_loader.dart`:
- Line 121: The OptimizedAssetLoader constructor should be made const to match
its parent RootBundleAssetLoader unless there's non-const initialization—update
the constructor declaration OptimizedAssetLoader({required String path}) :
super(path: path); to be const (i.e., add the const keyword) so instances can be
compile-time constants; if there is a deliberate reason it cannot be const, add
a brief comment in the class (near OptimizedAssetLoader) explaining why const
was omitted.

In `@lib/src/easy_localization_controller.dart`:
- Around line 126-138: The setLocale(Locale l) method currently drops concurrent
calls due to the _loading boolean guard; change it to either (A) queue the
latest requested locale by adding a _pendingLocale field and, if _loading is
true, assign _pendingLocale = l and return (or await completion then apply
pending), or (B) make setLocale return a status/future indicating it was skipped
by returning a bool/Result when _loading is true. Implement the chosen approach
inside setLocale and ensure you still call loadTranslations(),
notifyListeners(), EasyLocalization.logger(...) and _saveLocale(_locale) for the
applied locale, and clear/update _pendingLocale and the _loading flag (or
resolve awaiting callers) so no intermediate requests are silently lost.
- Around line 80-89: The catch block in loadTranslationData logs the original
stackTrace but throws a new TranslationLoadException without preserving it;
modify the error handling so the original stackTrace is carried into the thrown
exception—update the TranslationLoadException type (e.g., add an optional
StackTrace parameter or a factory like TranslationLoadException.withTrace) and
change the throw in loadTranslationData to throw
TranslationLoadException(locale, e, stackTrace) (or use the new factory),
keeping EasyLocalization.logger.error(...) as-is and ensuring _assetLoader.load
errors retain their original stack trace for downstream debugging.

In `@pubspec.yaml`:
- Line 42: The pubspec change tightened the intl constraint to ">=0.19.0
<0.21.0" which may force downstream consumers off 0.17/0.18; decide whether that
restriction is intentional and either revert or broaden the range. If you intend
to require >=0.19.0, add a short comment in pubspec.yaml or the PR description
explaining that Intl.defaultLocale and NumberFormat.currency compatibility was
verified for 0.19.x–0.20.x; otherwise change the dependency line for intl back
to include 0.17/0.18 (e.g., a wider lower bound) so downstreams aren’t forced to
upgrade. Ensure the dependency string referring to intl in pubspec.yaml and any
CI/pub upgrade notes reflect the chosen scope.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 30693c36-bd73-48fb-b002-302cd60a6eb9

📥 Commits

Reviewing files that changed from the base of the PR and between b5a12c3 and a02b3e6.

📒 Files selected for processing (18)
  • README.md
  • example/lib/lang_view.dart
  • lib/easy_localization.dart
  • lib/src/asset_loader.dart
  • lib/src/easy_localization_app.dart
  • lib/src/easy_localization_controller.dart
  • lib/src/easy_localization_storage_interface.dart
  • lib/src/exceptions.dart
  • lib/src/localization.dart
  • lib/src/public_ext.dart
  • lib/src/translations.dart
  • lib/src/widgets.dart
  • pubspec.yaml
  • test/easy_localization_context_test.dart
  • test/easy_localization_test.dart
  • test/easy_localization_widget_test.dart
  • test/root_bundle_asset_loader_test.dart
  • test/utils/test_asset_loaders.dart
✅ Files skipped from review due to trivial changes (2)
  • lib/src/widgets.dart
  • README.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • lib/easy_localization.dart
  • example/lib/lang_view.dart
  • test/root_bundle_asset_loader_test.dart
  • test/easy_localization_context_test.dart
  • test/utils/test_asset_loaders.dart

Comment thread lib/src/asset_loader.dart
Comment thread lib/src/asset_loader.dart
Comment thread lib/src/easy_localization_controller.dart
Comment thread lib/src/easy_localization_storage_interface.dart
Comment thread pubspec.yaml Outdated
Comment thread test/easy_localization_widget_test.dart
aissat added 12 commits June 9, 2026 22:34
Refactor test setup to include explicit supported locales and start
locales in EasyLocalization initializers to ensure consistent test
environments.
- Update CachedAssetLoader to inherit from RootBundleAssetLoader.
- Simplify OptimizedAssetLoader by utilizing base class caching.
- Add error handling for uninitialized SharedPreferences.
- Fix locale saving condition in EasyLocalizationController.
Reset EasyLocalization state after fallback locale tests and correct the

cache clearing reference in OptimizedAssetLoader tests.
@aissat aissat changed the title [V4]: Migrate to EasyLocalization v4 with improved initialization, asset loading, and storage v4.0.0-dev — API refactor, caching, fallback fixes, and benchmark suite Jun 9, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (5)
test/compatibility_test.dart (1)

15-23: ⚡ Quick win

These compatibility assertions should verify contract values, not only types.

The test names claim behavior (useOnlyLangCode, default path) but only assert isA<...>(). Please assert the actual forwarded/default values.

Concrete assertions to add
 test('withConfig respects useOnlyLangCode', () {
   final widget = EasyLocalizationCompat.withConfig(
     child: const Placeholder(),
     useOnlyLangCode: true,
     fallbackLocale: const Locale('fr', 'FR'),
   );
 
   expect(widget, isA<EasyLocalization>());
+  final easy = widget as EasyLocalization;
+  expect(easy.useOnlyLangCode, isTrue);
+  expect(easy.fallbackLocale, const Locale('fr', 'FR'));
 });
 
 test('migrateAssetLoader uses default path when not specified', () {
   final loader = EasyLocalizationCompat.migrateAssetLoader(
     type: AssetLoaderType.rootBundle,
   );
   expect(loader, isA<RootBundleAssetLoader>());
+  final root = loader as RootBundleAssetLoader;
+  expect(root.path, 'assets/translations');
 });

Also applies to: 36-41

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/compatibility_test.dart` around lines 15 - 23, The test currently only
checks the returned type from EasyLocalizationCompat.withConfig; update it to
assert that the returned EasyLocalization instance has the expected forwarded
configuration values: verify that useOnlyLangCode is true and fallbackLocale
equals Locale('fr','FR') for the first case by accessing the EasyLocalization
widget's properties (from the EasyLocalization instance returned by withConfig),
and similarly update the other test (lines 36-41) to assert the default path
value (e.g., 'assets/translations' or the library's default) and any other
default flags forwarded by withConfig rather than only using isA<...>(). Ensure
you locate the instance returned by EasyLocalizationCompat.withConfig and assert
on its useOnlyLangCode, fallbackLocale, path, and related config properties.
test/optimized_asset_loader_test.dart (1)

21-31: ⚡ Quick win

Cache behavior tests are currently non-verifying.

These cases only assert that loads throw, so they do not actually validate caching or per-locale cache separation. A regression in cache behavior would still pass these tests.

Suggested test direction
 test('caches translations across loads', () async {
   final loader = OptimizedAssetLoader(path: testPath);
   final locale = const Locale('en', 'US');

-  await expectLater(
-    () => loader.load(locale: locale),
-    throwsA(anything),
-  );
+  // Arrange a mock asset response and count loadString hits for this locale.
+  // Assert first load hits bundle once, second load returns cached data with no extra hit.
 });
 
 test('handles different locales independently', () async {
   final loader = OptimizedAssetLoader(path: testPath);
   final localeUS = const Locale('en', 'US');
   final localeFR = const Locale('fr', 'FR');
 
-  await expectLater(() => loader.load(locale: localeUS), throwsA(anything));
-  await expectLater(() => loader.load(locale: localeFR), throwsA(anything));
+  // Arrange distinct mock payloads for each locale path.
+  // Assert each locale is cached independently and returns its own data.
 });

Also applies to: 33-47

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/optimized_asset_loader_test.dart` around lines 21 - 31, The tests named
'caches translations across loads' (and the similar test around lines 33-47)
currently only assert that load() throws, so they don't verify caching or
per-locale isolation; update them to actually assert cache behavior by either
(a) creating real temporary translation files and calling
OptimizedAssetLoader.load(locale: ...) twice and asserting the loader returns
from cache on the second call (e.g., check a private/public cache accessor or
size if available) or (b) inject or spy a loader internals method used for disk
reads (mock the disk-read helper or wrap the file-read method) and assert it is
invoked only once per locale while two loads are requested for the same Locale
and invoked for two different Locales to assert separation; modify the tests to
use the OptimizedAssetLoader constructor and the specific methods load and the
internal cache accessor/spied method names to locate the code to change.
lib/src/asset_loader.dart (1)

37-72: 💤 Low value

CachedAssetLoader and RootBundleAssetLoader both maintain static caches, doubling memory usage.

CachedAssetLoader._translationCache (line 40) and RootBundleAssetLoader._cache (line 82) are both static Map<Locale, Map<String, dynamic>>. When CachedAssetLoader.load() calls super.load() at line 69, data is stored in RootBundleAssetLoader._cache, then again in _translationCache at line 70.

If the public translationCache getter is needed for testing/external access, consider having CachedAssetLoader skip the parent's caching by directly calling rootBundle.loadString() instead of super.load(), or make RootBundleAssetLoader check if a subclass already handles caching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/asset_loader.dart` around lines 37 - 72, CachedAssetLoader duplicates
RootBundleAssetLoader's static cache by calling super.load(), so change
CachedAssetLoader.load to bypass the parent's cache: instead of calling
super.load(locale: locale), use rootBundle.loadString(...) to read the asset for
the given Locale, decode the JSON into a Map<String, dynamic>, store it only
into CachedAssetLoader._translationCache and return it; keep the
translationCache getter and other helpers as-is so tests can access the cache
but avoid calling super.load() (and thus RootBundleAssetLoader._cache) to
prevent double-storing.
lib/src/compatibility.dart (1)

10-37: ⚡ Quick win

withConfig() is missing startLocale, saveLocale, and errorWidget parameters.

The v4 EasyLocalization widget retains these parameters (per PR objectives), but withConfig() doesn't expose them. Users migrating v3 code that used these features would need to bypass this helper.

Suggested enhancement
 static Widget withConfig({
   required Widget child,
   List<Locale>? supportedLocales,
   String? path,
   AssetLoader? assetLoader,
   Locale? fallbackLocale,
+  Locale? startLocale,
+  bool saveLocale = true,
   bool useOnlyLangCode = false,
   bool useFallbackTranslations = false,
+  Widget Function(FlutterError? message)? errorWidget,
 }) {
   // ... existing warning logic ...
   return EasyLocalization(
     fallbackLocale: fallbackLocale,
+    startLocale: startLocale,
+    saveLocale: saveLocale,
     useOnlyLangCode: useOnlyLangCode,
     useFallbackTranslations: useFallbackTranslations,
+    errorWidget: errorWidget,
     child: child,
   );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/compatibility.dart` around lines 10 - 37, Update the withConfig
signature to accept the missing parameters startLocale (Locale?), saveLocale
(bool?), and errorWidget (WidgetBuilder?) and forward them into the
EasyLocalization(...) constructor call (i.e., add startLocale: startLocale,
saveLocale: saveLocale, errorWidget: errorWidget) so that withConfig exposes the
same options as the EasyLocalization widget; keep the existing warnings for
supportedLocales/path but ensure the new parameters are declared with the same
types/names used by EasyLocalization to maintain parity for migrating v3 users.
example/v3_vs_v4_comparison.md (1)

114-127: 💤 Low value

Add language specifiers to code blocks.

The two code blocks showing directory structures are missing language specifiers. Consider adding text or leave them as plain text blocks for clarity.

📝 Suggested fix
-```
+```text
 assets/translations/
 ├── en.json     { "greeting": "Hello", ... }
 └── ar.json     { "greeting": "مرحبا", ... }

Or with country codes (when useOnlyLangCode: false, default):

- +text
assets/translations/
├── en-US.json
└── ar-DZ.json

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/v3_vs_v4_comparison.md` around lines 114 - 127, Update the two fenced
code blocks that show the assets/translations directory structure to include a
language specifier (e.g., ```text) so they render as plain text; specifically,
change the opening backticks for both directory-structure blocks (the ones
starting with "assets/translations/") to use ```text instead of plain ``` to
ensure proper formatting and clarity.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/playwright.yml:
- Around line 4-6: The workflow currently only triggers on branches: [ main,
master ] for both push and pull_request, so PRs targeting develop won’t run;
update the branch filters in the Playwright workflow by adding "develop" to both
branches arrays (the branches lists used for the push and pull_request triggers)
so the workflow will run for pushes and PRs against develop as well.
- Line 12: Update the Playwright workflow's checkout step to disable persisted
credentials by adding persist-credentials: false to the actions/checkout@v4
invocation; locate the existing uses: actions/checkout@v4 step in the workflow
and add the persist-credentials: false key under that step so the job does not
retain repo write credentials.
- Around line 12-13: The workflow uses floating tags for actions (uses:
actions/checkout@v4 and uses: actions/setup-node@v4) which must be pinned to
commit SHAs; update those two uses entries to reference the exact commit SHAs
from the respective GitHub repos (e.g., actions/checkout@<commit-sha> and
actions/setup-node@<commit-sha>) by looking up the desired release commit in
each action's repo and replacing the `@v4` tags with the corresponding SHAs so the
workflow no longer relies on retargetable tags.

In `@CHANGELOG.md`:
- Around line 3-15: Update the v4.0.0-dev changelog entry to explicitly list the
three removed features so they appear among the breaking changes: add entries
for useFallbackTranslationsForEmptyResources, ignorePluralRules, and
extraAssetLoaders (mention they were removed in v4), and ensure the phrasing
matches existing BREAKING entries and aligns with the
example/v3_vs_v4_comparison.md notes for consistency.

In `@example/v3_vs_v4_comparison.md`:
- Around line 139-141: Verify whether the three features
`useFallbackTranslationsForEmptyResources`, `ignorePluralRules`, and
`extraAssetLoaders` were actually removed in v4; if they were removed, add
explicit entries for each under the v4.0.0-dev BREAKING CHANGES section of
CHANGELOG.md describing the removal and any migration steps; if they are still
supported, remove these rows from the v3_vs_v4_comparison.md table to avoid
incorrect documentation. Ensure the CHANGELOG entries reference the exact
feature names above and briefly state the impact or recommended migration
action.

In `@pubspec.yaml`:
- Line 18: The SDK lower bound in pubspec.yaml (environment.sdk) is incompatible
with the declared intl range; update pubspec.yaml so either raise the Dart SDK
lower bound (environment.sdk) to a 3.x-compatible value (e.g., ">=3.0.0 <4.0.0")
or pin the intl dependency (the intl entry in dependencies) to a 2.x-compatible
range that supports Dart 2 (e.g., restrict to versions prior to 0.19), and
update the dependency constraint accordingly to ensure pub can resolve
dependencies for Dart 2.x users.

---

Nitpick comments:
In `@example/v3_vs_v4_comparison.md`:
- Around line 114-127: Update the two fenced code blocks that show the
assets/translations directory structure to include a language specifier (e.g.,
```text) so they render as plain text; specifically, change the opening
backticks for both directory-structure blocks (the ones starting with
"assets/translations/") to use ```text instead of plain ``` to ensure proper
formatting and clarity.

In `@lib/src/asset_loader.dart`:
- Around line 37-72: CachedAssetLoader duplicates RootBundleAssetLoader's static
cache by calling super.load(), so change CachedAssetLoader.load to bypass the
parent's cache: instead of calling super.load(locale: locale), use
rootBundle.loadString(...) to read the asset for the given Locale, decode the
JSON into a Map<String, dynamic>, store it only into
CachedAssetLoader._translationCache and return it; keep the translationCache
getter and other helpers as-is so tests can access the cache but avoid calling
super.load() (and thus RootBundleAssetLoader._cache) to prevent double-storing.

In `@lib/src/compatibility.dart`:
- Around line 10-37: Update the withConfig signature to accept the missing
parameters startLocale (Locale?), saveLocale (bool?), and errorWidget
(WidgetBuilder?) and forward them into the EasyLocalization(...) constructor
call (i.e., add startLocale: startLocale, saveLocale: saveLocale, errorWidget:
errorWidget) so that withConfig exposes the same options as the EasyLocalization
widget; keep the existing warnings for supportedLocales/path but ensure the new
parameters are declared with the same types/names used by EasyLocalization to
maintain parity for migrating v3 users.

In `@test/compatibility_test.dart`:
- Around line 15-23: The test currently only checks the returned type from
EasyLocalizationCompat.withConfig; update it to assert that the returned
EasyLocalization instance has the expected forwarded configuration values:
verify that useOnlyLangCode is true and fallbackLocale equals Locale('fr','FR')
for the first case by accessing the EasyLocalization widget's properties (from
the EasyLocalization instance returned by withConfig), and similarly update the
other test (lines 36-41) to assert the default path value (e.g.,
'assets/translations' or the library's default) and any other default flags
forwarded by withConfig rather than only using isA<...>(). Ensure you locate the
instance returned by EasyLocalizationCompat.withConfig and assert on its
useOnlyLangCode, fallbackLocale, path, and related config properties.

In `@test/optimized_asset_loader_test.dart`:
- Around line 21-31: The tests named 'caches translations across loads' (and the
similar test around lines 33-47) currently only assert that load() throws, so
they don't verify caching or per-locale isolation; update them to actually
assert cache behavior by either (a) creating real temporary translation files
and calling OptimizedAssetLoader.load(locale: ...) twice and asserting the
loader returns from cache on the second call (e.g., check a private/public cache
accessor or size if available) or (b) inject or spy a loader internals method
used for disk reads (mock the disk-read helper or wrap the file-read method) and
assert it is invoked only once per locale while two loads are requested for the
same Locale and invoked for two different Locales to assert separation; modify
the tests to use the OptimizedAssetLoader constructor and the specific methods
load and the internal cache accessor/spied method names to locate the code to
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6eba10fc-1366-498e-a38b-5f8b320b0021

📥 Commits

Reviewing files that changed from the base of the PR and between a02b3e6 and 138dea3.

📒 Files selected for processing (14)
  • .github/workflows/playwright.yml
  • .gitignore
  • CHANGELOG.md
  • easy_localization.iml
  • example/.metadata
  • example/v3_vs_v4_comparison.md
  • lib/src/asset_loader.dart
  • lib/src/compatibility.dart
  • lib/src/easy_localization_controller.dart
  • lib/src/easy_localization_storage_interface.dart
  • pubspec.yaml
  • test/compatibility_test.dart
  • test/easy_localization_widget_test.dart
  • test/optimized_asset_loader_test.dart
💤 Files with no reviewable changes (3)
  • example/.metadata
  • easy_localization.iml
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/src/easy_localization_storage_interface.dart
  • test/easy_localization_widget_test.dart

Comment on lines +4 to +6
branches: [ main, master ]
pull_request:
branches: [ main, master ]

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 | 🟠 Major | ⚡ Quick win

Workflow won’t run for develop-targeted PRs.

This workflow only listens to main/master, but this PR targets develop; tests won’t execute on that integration path unless develop is added.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/playwright.yml around lines 4 - 6, The workflow currently
only triggers on branches: [ main, master ] for both push and pull_request, so
PRs targeting develop won’t run; update the branch filters in the Playwright
workflow by adding "develop" to both branches arrays (the branches lists used
for the push and pull_request triggers) so the workflow will run for pushes and
PRs against develop as well.

timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

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 | 🟠 Major | ⚡ Quick win

Disable persisted checkout credentials for this read-only test job.

Set persist-credentials: false on actions/checkout; this job does not need repo write credentials and reducing token exposure hardens the pipeline.

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 12-12: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 12-12: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/playwright.yml at line 12, Update the Playwright
workflow's checkout step to disable persisted credentials by adding
persist-credentials: false to the actions/checkout@v4 invocation; locate the
existing uses: actions/checkout@v4 step in the workflow and add the
persist-credentials: false key under that step so the job does not retain repo
write credentials.

Source: Linters/SAST tools

Comment on lines +12 to +13
- uses: actions/checkout@v4
- uses: actions/setup-node@v4

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 | 🟠 Major | ⚡ Quick win

Pin GitHub Actions to commit SHAs instead of floating tags.

Using @v4 leaves the workflow open to upstream tag retargeting/supply-chain risk and violates the reported policy check.

Also applies to: 22-22

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 12-12: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 12-12: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 13-13: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/playwright.yml around lines 12 - 13, The workflow uses
floating tags for actions (uses: actions/checkout@v4 and uses:
actions/setup-node@v4) which must be pinned to commit SHAs; update those two
uses entries to reference the exact commit SHAs from the respective GitHub repos
(e.g., actions/checkout@<commit-sha> and actions/setup-node@<commit-sha>) by
looking up the desired release commit in each action's repo and replacing the
`@v4` tags with the corresponding SHAs so the workflow no longer relies on
retargetable tags.

Source: Linters/SAST tools

Comment thread CHANGELOG.md
Comment thread example/v3_vs_v4_comparison.md
Comment thread pubspec.yaml

environment:
sdk: '>=2.12.0 <4.0.0'
sdk: ">=2.17.0 <4.0.0"

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify intl version metadata directly from pub.dev (read-only).
curl -s https://pub.dev/api/packages/intl | jq '.latest.version, .versions[] | select(.version=="0.19.0" or .version=="0.20.0") | {version, sdk: .pubspec.environment.sdk}'

Repository: aissat/easy_localization

Length of output: 136


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Fetch intl package metadata from pub.dev (read-only) and inspect JSON shape first.
curl -s https://pub.dev/api/packages/intl | jq 'keys'

# Show structure of a single versions element (if present).
curl -s https://pub.dev/api/packages/intl | jq '.versions | type, (.[0] | keys)'

# Extract the pubspec.environment.sdk for specific versions.
curl -s https://pub.dev/api/packages/intl | jq '
  [0.19.0, 0.20.0] as $wanted
  | {matches: [.versions[]
      | select(.version? and ($wanted|index(.version)) != null)
      | {version: .version, sdk: (.pubspec.environment.sdk? // .pubspec.environment? // null)}]]
'

Repository: aissat/easy_localization

Length of output: 845


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show available intl versions in the 0.19.x / 0.20.x range.
curl -s https://pub.dev/api/packages/intl | jq -r '
  .versions
  | map(.version)
  | map(select(startswith("0.19.") or startswith("0.20.")))
  | sort
  | .[]
'

# Extract pubspec.environment.sdk constraints for intl 0.19.0 and 0.20.0 (quoted strings).
curl -s https://pub.dev/api/packages/intl | jq '
  [ "0.19.0", "0.20.0" ] as $wanted
  | .versions
  | map(select(.version as $v | $wanted | index($v) != null))
  | map({version: .version, sdk: (.pubspec.environment.sdk? // .pubspec.environment? // null)})
'

# Also extract any versions in repo constraint range >=0.19.0 <0.21.0 (best-effort).
curl -s https://pub.dev/api/packages/intl | jq '
  .versions
  | map(select(.version? != null))
  | map({version: .version, sdk: (.pubspec.environment.sdk? // .pubspec.environment? // null)})
  | sort_by(.version)
  | map(select(.version >= "0.19.0" and .version < "0.21.0"))
'

Repository: aissat/easy_localization

Length of output: 629


🏁 Script executed:

#!/bin/bash
set -euo pipefail
curl -s https://pub.dev/api/packages/intl | jq '
  [ "0.20.1", "0.20.2" ] as $wanted
  | .versions
  | map(select(.version as $v | $wanted | index($v) != null))
  | map({version: .version, sdk: (.pubspec.environment.sdk? // .pubspec.environment? // null)})
'

Repository: aissat/easy_localization

Length of output: 179


Align Dart SDK lower bound with intl’s Dart 3+ requirement

pubspec.yaml sets environment.sdk: ">=2.17.0 <4.0.0", but intl: ">=0.19.0 <0.21.0" can resolve to intl 0.19.0–0.20.2, whose pubspec.environment.sdk is ^3.0.0 / ^3.3.0. This can break pub dependency resolution for consumers on Dart 2.17–2.19. Align these constraints by either raising the Dart minimum (e.g., >=3.0.0) or tightening intl to versions that support Dart 2.x.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pubspec.yaml` at line 18, The SDK lower bound in pubspec.yaml
(environment.sdk) is incompatible with the declared intl range; update
pubspec.yaml so either raise the Dart SDK lower bound (environment.sdk) to a
3.x-compatible value (e.g., ">=3.0.0 <4.0.0") or pin the intl dependency (the
intl entry in dependencies) to a 2.x-compatible range that supports Dart 2
(e.g., restrict to versions prior to 0.19), and update the dependency constraint
accordingly to ensure pub can resolve dependencies for Dart 2.x users.

aissat added 2 commits June 9, 2026 23:37
- Added a library-level description to easy_localization.dart.
- Restricted intl package exports to specific classes.
- Added a macro reference to the plural extension method.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/easy_localization.dart`:
- Line 10: The export in lib/easy_localization.dart now only re-exports Intl,
DateFormat, and NumberFormat which is a breaking change for consumers who relied
on other package:intl/intl.dart symbols; update the project docs to call this
out and provide migration guidance (add a CHANGELOG.md entry and a README note)
stating that easy_localization no longer re-exports Bidi, DateSymbols,
NumberSymbols, message-lookup classes, etc., and advise consumers to import
package:intl/intl.dart directly or restore the broader re-export if you intend
to keep previous surface area; reference the export statement in
lib/easy_localization.dart and make the docs change alongside a clear "Breaking
change" header and suggested code examples for consumers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 23280047-9a5e-4eb3-b086-e67ddf5d643f

📥 Commits

Reviewing files that changed from the base of the PR and between 138dea3 and d2da41b.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • lib/easy_localization.dart
  • lib/src/easy_localization_controller.dart
  • lib/src/plural_rules.dart
  • lib/src/public_ext.dart
  • pubspec.yaml
✅ Files skipped from review due to trivial changes (1)
  • lib/src/plural_rules.dart
🚧 Files skipped from review as they are similar to previous changes (4)
  • lib/src/public_ext.dart
  • pubspec.yaml
  • CHANGELOG.md
  • lib/src/easy_localization_controller.dart

export 'package:intl/intl.dart';
export 'package:easy_localization/src/easy_localization_storage_interface.dart';
export 'package:easy_localization/src/compatibility.dart';
export 'package:intl/intl.dart' show Intl, DateFormat, NumberFormat;

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for intl API usage beyond Intl, DateFormat, NumberFormat

# Check for Bidi class usage
rg -n '\bBidi\b' --type dart --glob '!**/test/**'

# Check for TextDirection from intl (note: Flutter also has TextDirection)
rg -n "import.*intl.*TextDirection" --type dart

# Check for DateSymbols, NumberSymbols usage
rg -n '\b(DateSymbols|NumberSymbols)\b' --type dart --glob '!**/test/**'

# Check for Intl message classes
rg -n '\b(IntlMessage|MessageLookup|CompositeMessage)\b' --type dart --glob '!**/test/**'

Repository: aissat/easy_localization

Length of output: 50


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the export line in question
echo "=== lib/easy_localization.dart (around line 10) ==="
nl -ba lib/easy_localization.dart | sed -n '1,60p'

# 2) Search for other intl symbols/types used anywhere in the repo (non-test)
echo
echo "=== Search: other intl types used ==="
rg -n --type dart --glob '!**/test/**' '\b(Bidi|DateSymbols|NumberSymbols|MessageLookup|CompositeMessage|IntlMessage|TextDirection)\b' . || true

# 3) Search for intl imports/usages patterns
echo
echo "=== Search: imports from intl ==="
rg -n --type dart --glob '!**/test/**' 'import\s+['"]package:intl/intl.dart['"]' . || true
rg -n --type dart --glob '!**/test/**' 'package:intl/intl.dart.*\b' . || true

# 4) Search for the export narrowing in docs/changelog
echo
echo "=== Search: changelog/migration docs mention intl export narrowing ==="
fd -a "CHANGELOG*|changelog*|MIGRATION*|migration*|UPGRADING*|upgrading*|README*|roadmap*" . -t f 2>/dev/null || true
rg -n --glob '!*test/**' 'intl\.|easy_localization.*intl|DateFormat|NumberFormat|Bidi|TextDirection|DateSymbols|NumberSymbols|MessageLookup|CompositeMessage' CHANGELOG* **/*CHANGELOG* **/*migration* **/*MIGRATION* **/*README* **/*UPGRADING* 2>/dev/null || true

Repository: aissat/easy_localization

Length of output: 181


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the export line in question (no `nl` available)
echo "=== lib/easy_localization.dart (first ~60 lines) ==="
awk 'NR>=1 && NR<=60 {printf "%d:%s\n", NR, $0}' lib/easy_localization.dart | sed -n '1,60p'

# 2) Search for other intl symbols/types used anywhere in the repo (non-test)
echo
echo "=== Search: other intl types used (non-test) ==="
rg -n --type dart --glob '!**/test/**' '\b(Bidi|DateSymbols|NumberSymbols|MessageLookup|CompositeMessage|IntlMessage|TextDirection)\b' . || true

# 3) Search for intl imports/usages patterns
echo
echo "=== Search: imports from package:intl/intl.dart (non-test) ==="
rg -n --type dart --glob '!**/test/**' 'import\s+['"'"'"]package:intl/intl.dart['"'"'"]' . || true
rg -n --type dart --glob '!**/test/**' 'package:intl/intl.dart.*\b' . || true

# 4) Search for the export narrowing mention in changelog/migration docs
echo
echo "=== Search: changelog/migration docs mention intl export narrowing ==="
# List likely docs/changelogs first
fd . CHANGELOG* changelog* MIGRATION* migration* UPGRADING* upgrading* README* 2>/dev/null || true

rg -n --glob '!*test/**' \
  'intl\.|easy_localization.*intl|DateFormat|NumberFormat|Bidi|TextDirection|DateSymbols|NumberSymbols|MessageLookup|CompositeMessage' \
  CHANGELOG* **/*CHANGELOG* **/*migration* **/*MIGRATION* **/*README* **/*UPGRADING* 2>/dev/null || true

Repository: aissat/easy_localization

Length of output: 1491


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== lib/src/public.dart (first 200 lines) ==="
awk 'NR<=200 {printf "%d:%s\n", NR, $0}' lib/src/public.dart

echo
echo "=== lib/src/public_ext.dart (first 200 lines) ==="
awk 'NR<=200 {printf "%d:%s\n", NR, $0}' lib/src/public_ext.dart

echo
echo "=== lib/src/widgets.dart (around TextDirection usage) ==="
rg -n "TextDirection\.ltr" lib/src/widgets.dart
sed -n '1,120p' lib/src/widgets.dart

echo
echo "=== Explicit searches for other intl symbols (non-test) ==="
for sym in Bidi DateSymbols NumberSymbols MessageLookup CompositeMessage IntlMessage; do
  echo "-- $sym --"
  rg -n --type dart --glob '!**/test/**' "\b${sym}\b" . || true
done

echo
echo "=== Repo-wide search for intl re-exports / narrowed show export ==="
rg -n --type dart --glob '!**/test/**' "export\s+'package:intl/intl\.dart'\s+show" . || true
rg -n --type dart --glob '!**/test/**' "show\s+Intl,\s*DateFormat,\s*NumberFormat" . || true
rg -n --type dart --glob '!**/test/**' "package:intl/intl\.dart'\s+show" . || true

echo
echo "=== Find changelog/migration/upgrading docs filenames ==="
fd . -t f -i "changelog|migration|upgrading|upgrade" || true

echo
echo "=== Search docs for intl export narrowing / migration notes ==="
rg -n --glob '!*test/**' '(intl\.dart|DateFormat|NumberFormat|Bidi|TextDirection|DateSymbols|NumberSymbols|MessageLookup|CompositeMessage)' \
  CHANGELOG* **/CHANGELOG* **/*migration* **/*MIGRATION* **/*upgrad* **/*README* **/*UPGRAD* 2>/dev/null || true

Repository: aissat/easy_localization

Length of output: 13597


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Repo top-level files ==="
ls -la

echo
echo "=== Candidate changelog/migration/upgrading files ==="
fd -t f -i '(changelog|migration|upgrading|upgrad(es?)|release|news)\b' . || true

echo
echo "=== Search docs for intl export narrowing / breaking change ==="
rg -n --glob '!*test/**' '(intl\.dart|package:intl/intl\.dart|DateFormat|NumberFormat|Bidi|DateSymbols|NumberSymbols|MessageLookup|CompositeMessage)\b' \
  --glob '*README*' --glob '*CHANGELOG*' --glob '*MIGRATION*' --glob '*UPGRADING*' . || true

echo
echo "=== Search for 'breaking' + 'intl' wording ==="
rg -n --glob '!*test/**' '(breaking|migration|upgrade|changelog)\b.*\bintl\b' . || true

Repository: aissat/easy_localization

Length of output: 2463


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== CHANGELOG.md: search for intl/breaking/migration/export keywords ==="
rg -n --glob '*CHANGELOG*' '(intl|package:intl/intl\.dart|breaking|migration|upgrade|export)' CHANGELOG.md || true

echo
echo "=== README.md: search for intl/export keywords ==="
rg -n '(intl|package:intl/intl\.dart|export.*intl|migration|upgrade|breaking)' README.md example/README.md || true

Repository: aissat/easy_localization

Length of output: 959


Document the breaking impact of the narrowed intl re-export (lib/easy_localization.dart:10).

  • Repo usage check: no references to other package:intl/intl.dart symbols such as Bidi, DateSymbols, NumberSymbols, or message-lookup classes—only the narrowed Intl/DateFormat/NumberFormat surface is used internally (Flutter TextDirection is unrelated).
  • Documentation check: CHANGELOG.md/README do not mention that easy_localization now re-exports only Intl, DateFormat, and NumberFormat, so consumers relying on other intl exports through easy_localization won’t have a migration path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/easy_localization.dart` at line 10, The export in
lib/easy_localization.dart now only re-exports Intl, DateFormat, and
NumberFormat which is a breaking change for consumers who relied on other
package:intl/intl.dart symbols; update the project docs to call this out and
provide migration guidance (add a CHANGELOG.md entry and a README note) stating
that easy_localization no longer re-exports Bidi, DateSymbols, NumberSymbols,
message-lookup classes, etc., and advise consumers to import
package:intl/intl.dart directly or restore the broader re-export if you intend
to keep previous surface area; reference the export statement in
lib/easy_localization.dart and make the docs change alongside a clear "Breaking
change" header and suggested code examples for consumers.

aissat added 4 commits June 13, 2026 14:35
Introduces a suite of functions for resolving nested translation keys,
handling link references, and performing argument substitution. These
functions operate on plain maps and strings, ensuring they are
environment-agnostic and easy to test.
- Remove commented-out code and unused TODOs
- Improve code formatting and style consistency

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/easy_logger/pubspec.yaml (1)

16-16: Consider removing redundant matcher dev dependency
packages/easy_logger/pubspec.yaml adds matcher: ^0.12.19 (line 16), but there are no direct package:matcher imports anywhere under packages/easy_logger/—so it’s likely provided transitively (e.g., via flutter_test). Drop it unless you’re intentionally pinning a specific matcher version for your tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/easy_logger/pubspec.yaml` at line 16, The pubspec dev_dependency
entry for "matcher: ^0.12.19" is redundant in the easy_logger package; remove
the "matcher" dev_dependencies entry from the package's pubspec.yaml (unless you
intentionally need that specific version for tests), then run pub get and run
the package's tests to ensure nothing breaks; if a test actually requires
matcher, instead document the reason in a comment near the dev_dependencies
entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/src/resolver.dart`:
- Around line 27-29: The _capitalize function can crash on empty input; update
_capitalize(String val) to first check val.isEmpty and return val (or '')
immediately, otherwise construct '${val[0].toUpperCase()}${val.substring(1)}';
optionally apply the same defensive empty-string check to _upper and _lower for
consistency (though toUpperCase()/toLowerCase() already handle empty strings) so
all three helpers are safe to call with empty input.

---

Nitpick comments:
In `@packages/easy_logger/pubspec.yaml`:
- Line 16: The pubspec dev_dependency entry for "matcher: ^0.12.19" is redundant
in the easy_logger package; remove the "matcher" dev_dependencies entry from the
package's pubspec.yaml (unless you intentionally need that specific version for
tests), then run pub get and run the package's tests to ensure nothing breaks;
if a test actually requires matcher, instead document the reason in a comment
near the dev_dependencies entry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 18a02147-ef7f-4c1e-9e63-468170605efa

📥 Commits

Reviewing files that changed from the base of the PR and between d2da41b and 1e59e00.

📒 Files selected for processing (9)
  • lib/src/resolver.dart
  • packages/easy_logger/pubspec.yaml
  • test/easy_localization_context_test.dart
  • test/easy_localization_ctl_test.dart
  • test/easy_localization_test.dart
  • test/easy_localization_widget_test.dart
  • test/optimized_asset_loader_test.dart
  • test/resolver_test.dart
  • test/root_bundle_asset_loader_test.dart
💤 Files with no reviewable changes (1)
  • test/easy_localization_widget_test.dart
🚧 Files skipped from review as they are similar to previous changes (5)
  • test/optimized_asset_loader_test.dart
  • test/easy_localization_ctl_test.dart
  • test/root_bundle_asset_loader_test.dart
  • test/easy_localization_context_test.dart
  • test/easy_localization_test.dart

Comment thread lib/src/resolver.dart Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants