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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 102 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Easy and Fast internationalization for your Flutter Apps
- 💻 Code generation for localization files and keys.
- 🛡️ Null safety
- 🖨️ Customizable logger.
- 🗄️ Pluggable storage (`SharedPreferencesStorage`, `InMemoryStorage`, or custom).

## Getting Started

Expand Down Expand Up @@ -102,14 +103,17 @@ import 'package:easy_localization/easy_localization.dart';

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

await EasyLocalization.ensureInitialized(
assetLoader: RootBundleAssetLoader(
path: 'assets/translations',
supportedLocales: [Locale('en', 'US'), Locale('de', 'DE')],
),
);

runApp(
EasyLocalization(
supportedLocales: [Locale('en', 'US'), Locale('de', 'DE')],
path: 'assets/translations', // <-- change the path of the translation files
fallbackLocale: Locale('en', 'US'),
child: MyApp()
child: MyApp(),
),
);
}
Expand All @@ -135,9 +139,7 @@ class MyApp extends StatelessWidget {
| ----------------------- | -------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| key | false | | Widget key. |
| child | true | | Place for your main page widget. |
| supportedLocales | true | | List of supported locales. |
| path | true | | Path to your folder with localization files. |
| assetLoader | false | `RootBundleAssetLoader()` | Class loader for localization files. You can use custom loaders from [Easy Localization Loader](https://github.com/aissat/easy_localization_loader) or create your own class. |
| assetLoader | true | | Class loader for localization files. Passes `path` and `supportedLocales` via `RootBundleAssetLoader`. You can use custom loaders from [Easy Localization Loader](https://github.com/aissat/easy_localization_loader) or create your own class. |
| fallbackLocale | false | | Returns the locale when the locale is not in the list `supportedLocales`. |
| startLocale | false | | Overrides device locale. |
| saveLocale | false | `true` | Save locale in device storage. |
Expand All @@ -149,21 +151,98 @@ class MyApp extends StatelessWidget {

### 🔥 Initialize library

Call `EasyLocalization.ensureInitialized()` in your main before runApp.
Call `EasyLocalization.ensureInitialized()` in your main before runApp. Pass an `assetLoader` with your translation `path` and `supportedLocales`.

```dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();

await EasyLocalization.ensureInitialized(
assetLoader: RootBundleAssetLoader(
path: 'assets/translations',
supportedLocales: [Locale('en', 'US'), Locale('de', 'DE')],
),
);

runApp(EasyLocalization(
fallbackLocale: Locale('en', 'US'),
child: MyApp(),
));
}
```

### 🗄️ Storage

The selected locale can be persisted across app restarts using a pluggable storage backend.

#### `IEasyLocalizationStorage`

Abstract interface with two implementations:

| Implementation | Backend | Use case |
|---|---|---|
| `SharedPreferencesStorage` | `shared_preferences` | Production (default when omitted) |
| `InMemoryStorage` | In-memory map | Tests, ephemeral use |

Pass storage to `ensureInitialized()`:

```dart
void main() async{
// ...
// Needs to be called so that we can await for EasyLocalization.ensureInitialized();
void main() async {
WidgetsFlutterBinding.ensureInitialized();

await EasyLocalization.ensureInitialized();
// ...
runApp(....)
// ...
await EasyLocalization.ensureInitialized(
assetLoader: RootBundleAssetLoader(
path: 'assets/translations',
supportedLocales: [Locale('en', 'US'), Locale('de', 'DE')],
),
storage: SharedPreferencesStorage(), // optional
);

runApp(EasyLocalization(
fallbackLocale: Locale('en', 'US'),
child: MyApp(),
));
}
```

#### Custom storage

Implement `IEasyLocalizationStorage` for any backend:

```dart
class MyStorage implements IEasyLocalizationStorage {
@override
Future<void> init() async { /* open connection */ }

@override
Future<String?> getValue(String key) async { /* read */ }

@override
Future<void> setValue(String key, String value) async { /* write */ }

@override
Future<void> removeValue(String key) async { /* delete */ }

@override
Future<void> close() async { /* cleanup */ }
}
```

#### Testing with `InMemoryStorage`

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

// In-memory — no mock setup needed
print(await storage.getValue('locale')); // null
```

---

### 🔥 Change or get locale

Easy localization uses extension methods [BuildContext] for access to locale.
Expand Down Expand Up @@ -417,7 +496,7 @@ RaisedButton(

At any time, you can take the main [properties](#-easy-localization-widget-properties) of the Easy localization widget using [BuildContext].

Are supported: supportedLocales, fallbackLocale, localizationDelegates.
Are supported: supportedLocales, fallbackLocale, localizationDelegates, assetLoader.

Example:

Expand Down Expand Up @@ -454,12 +533,14 @@ Steps:
```dart
import 'generated/codegen_loader.g.dart';
...
void main(){
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized(
assetLoader: CodegenLoader(),
);
runApp(EasyLocalization(
child: MyApp(),
supportedLocales: [Locale('en', 'US'), Locale('ar', 'DZ')],
path: 'resources/langs',
assetLoader: CodegenLoader()
fallbackLocale: Locale('en', 'US'),
));
}
...
Expand Down
2 changes: 1 addition & 1 deletion bin/generate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ class CodegenLoader extends AssetLoader{
const CodegenLoader();

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

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

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

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

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

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

@override
// TODO: implement path
String get path => throw UnimplementedError();
}
57 changes: 36 additions & 21 deletions example/lib/lang_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class LanguageView extends StatelessWidget {
'',
style: TextStyle(color: Colors.black),
),
backgroundColor: Colors.white,
// backgroundColor: Colors.white,
iconTheme: IconThemeData(color: Colors.black),
elevation: 0,
),
Expand All @@ -29,7 +29,7 @@ class LanguageView extends StatelessWidget {
child: Text(
'Choose language',
style: TextStyle(
color: Colors.blue,
color: Theme.of(context).primaryColor,
fontFamily: 'Montserrat',
fontWeight: FontWeight.w700,
fontSize: 18,
Expand Down Expand Up @@ -97,26 +97,41 @@ class _SwitchListTileMenuItem extends StatelessWidget {

@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(left: 10, right: 10, top: 5),
decoration: BoxDecoration(
border:
isSelected(context) ? Border.all(color: Colors.blueAccent) : null,
return Material(
type: MaterialType.transparency,
child: Container(
margin: EdgeInsets.symmetric(vertical: 2, horizontal: 24),
decoration: BoxDecoration(
border: isSelected(context)
? Border.all(color: Theme.of(context).primaryColor)
: null,
),
child: ListTile(
dense: true,
title: Text(
title,
),
subtitle: Text(
subtitle,
),
onTap: () async {
log(locale.toString(), name: toString());
final sw = Stopwatch()..start();
await context.setLocale(locale); //BuildContext extension method
sw.stop();
final ms = sw.elapsedMicroseconds / 1000;
debugPrint('BENCH\tswitch (interactive: ${locale.toString().padRight(8)})\t${ms.toStringAsFixed(2)} ms');
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Switch time: ${ms.toStringAsFixed(2)} ms'),
duration: const Duration(seconds: 1),
),
);
Navigator.pop(context);
}
}),
),
child: ListTile(
dense: true,
// isThreeLine: true,
title: Text(
title,
),
subtitle: Text(
subtitle,
),
onTap: () async {
log(locale.toString(), name: toString());
await context.setLocale(locale); //BuildContext extension method
Navigator.pop(context);
}),
);
}
}
36 changes: 17 additions & 19 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,8 @@ import 'lang_view.dart';

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

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

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

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

Copilot AI Aug 11, 2025

Copy link

Choose a reason for hiding this comment

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

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

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

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

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

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

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

# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading