Open-source project
aissat/easy_localization avatar
aissat/easy_localization

easy_localization: runtime translation loading for Flutter apps

Easy and Fast internationalizing your Flutter Apps

1,051 stars373 forksDartMIT

At a glance

What is it?
easy_localization is a Flutter package that loads translation files from assets at runtime instead of generating them at build time, and adds plural, gender, nesting, fallback locales and an error widget for missing keys. It suits apps whose locale list changes between builds, and it costs you a runtime asset load where a generated class would have given you compile time checks.
Who is it for?
easy_localization fits Flutter teams that want to add or change a locale by dropping a file into assets/translations, and that accept runtime loading in exchange for it. It does not fit a team that needs the compiler to catch a missing or misspelled key before release, because lookup happens at runtime against a string path.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 132 days ago.
What is it written in?
Mainly Dart, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Translations are loaded from assets at runtime, not generated into the binary

easy_localization is a Flutter package for internationalizing an app, published on pub.dev and licensed under MIT. Its defining choice is where the translations live. Instead of a build time step that turns a set of arb or json resources into a generated Dart class with typed accessors, this package reads the translation files at runtime from the asset bundle and resolves a key when a widget asks for it. The consequence is easy to state: adding a language means adding a file, not regenerating code and fixing compile errors. The other consequence is that a missing or misspelled key is discovered on screen, not by the compiler. The feature list names plural forms, gender selection, nested keys, right to left locale handling, fallback locale key redirection, an error widget that reports missing translations, a customizable logger, null safety, extension methods on Text and on BuildContext, and code generation support for localization files and keys. The repository layout shows a bin/ directory and an i18n/ directory, alongside an example/ app that contains a full working setup, and a CHANGELOG.md and CONTRIBUTING.md for the usual project hygiene. The last push was on 2026-06-13, and the most recent published release listed is v3.0.8 from 2025-07-24.

Declaring the package and the asset directory in pubspec.yaml

Installation is two entries in pubspec.yaml. The dependency line uses a placeholder for the version, and the translation folder is then declared as an asset so that the files are bundled with the app.

yaml
dependencies:
  easy_localization: <last_version>
yaml
flutter:
  assets:
    - assets/translations/

The file naming convention is the part that trips people up. Each file is named either by language code alone or by a full locale code, with the extension matching the format you chose.

code
assets
└── translations
    ├── en.json
    └── en-US.json

The asset declaration matters more than it looks. If assets/translations is not listed under flutter assets, the loader finds nothing at runtime and every lookup fails, which surfaces as missing keys rather than as a packaging error. The README also points to a separate project, Easy Localization Loader, for pulling translations from JSON, CSV, HTTP, XML and YAML sources, and for custom loaders that read from somewhere other than the asset bundle. That loader is optional. The default assetLoader is RootBundleAssetLoader, and extraAssetLoaders exists as a list if a dependency package ships its own translations that also need loading.

Wrapping the app with EasyLocalization and reading the locale from context

The package works by wrapping your app in an EasyLocalization widget and then reading the resolved locale from the build context. Initialization is asynchronous, so main awaits it before runApp, and MaterialApp then takes its localizations from the context rather than from a hard coded list.

dart
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:easy_localization/easy_localization.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();
  
  runApp(
    EasyLocalization(
      supportedLocales: [Locale('en', 'US'), Locale('de', 'DE')],
      path: 'assets/translations',
      fallbackLocale: Locale('en', 'US'),
      child: MyApp()
    ),
  );
}
dart
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      home: MyHomePage()
    );
  }
}

Three properties are required on the wrapper: child, supportedLocales and path. Everything else has a default, with assetLoader defaulting to RootBundleAssetLoader. The imports are worth noticing, because the example still pulls in package:flutter_localizations/flutter_localizations.dart and hands context.localizationDelegates to MaterialApp. The package does not replace the framework's localization machinery, it sits in front of it and supplies the resolved locale and the delegates. Locale changes are meant to be observed and persisted, so a user switching language keeps that choice across restarts, which the feature list describes as reacting to and persisting locale changes.

Key lookup, fallbacks and the missing translation error widget

Because the translations are a map of string keys to values rather than generated members, the failure modes are specific. A key that does not exist in the current locale falls back according to the fallbackLocale you configured, and the fallback locale keys redirection feature means a key present only in the fallback file still resolves rather than throwing. The trade-off is that a fallback can hide a real gap. If a translator forgets a key in de-DE and the fallback is en-US, the screen shows English text and nothing reports the omission. That is exactly what the error widget for missing translations is for: it is a deliberate alternative to the fallback, letting you surface a missing key on screen during development instead of silently substituting another language. The extension methods on Text and on BuildContext are the ergonomic layer, giving you a translated string without assembling a key path and a locale by hand. The customizable logger sits alongside it for teams that want the package to report what it is doing, and the null safety claim means the lookup API returns values the compiler can reason about, which is a different guarantee from compile time verification of key names. If you rely on the error widget, remember it changes what a user sees when a translation is missing, so it is usually a development setting rather than a shipped one.

iOS needs CFBundleLocalizations declared in Info.plist

The one platform specific step in the documentation is iOS. For translation to work there, supported locales have to be added to ios/Runner/Info.plist under the CFBundleLocalizations key, following the Flutter guidance on specifying supported locales.

xml
<key>CFBundleLocalizations</key>
<array>
	<string>en</string>
	<string>nb</string>
</array>

This is a genuine trap, because the failure does not look like a localization bug. The app builds, the widget tree renders, and the picker may list the locale, yet the system itself does not treat the language as supported. On Android and web the same step is not needed because the asset bundle is enough. The example project in this repository contains android, ios, linux, macos and web directories, which makes it a convenient reference for the platform folder layout, and it includes an example/resources directory alongside example/lib, example/test and its own pubspec.yaml. Note that the plist list and the supportedLocales list in Dart are two separate declarations of the same intent, and nothing in the documentation says the package checks one against the other.

Runtime loading compared with a generated localization class

The alternative to this approach is not another package name so much as the other architectural choice: generate a Dart class from your translation sources at build time and let the compiler check every key. The difference in approach is straightforward. Generation gives you a typed accessor per key, so a renamed key is a compile error, and the lookup needs no asset bundle at runtime. Runtime loading gives you the opposite: files are read when the app starts, a new locale is a new file, and there is no generated code to keep in step with the assets. The cost sits in different places. With generation, changing a translation string requires regenerating code and rebuilding, and a typo in a key is caught immediately. With runtime loading, a typo is caught by a user, unless the error widget is on. Teams shipping frequent copy changes to translators who edit json files directly tend to prefer the runtime model, because a translator can add a language without touching Dart. Teams where key stability is enforced by review tend to prefer generation, because the compiler is doing the policing for free.

Where easy_localization is the wrong tool

There are clear cases for staying away. If your app must never show a raw key or a blank string to a user, runtime lookup with a silent fallback is the wrong default, and the error widget is a mitigation rather than a fix. If you need plural and gender rules that follow a locale's grammar precisely, this package handles plural, gender and nesting as features but the documentation here does not describe the exact rule set, so verify the behaviour for the specific locales you ship before betting on it. If your application is offline only and small, the async load at startup is work you could have avoided. If your team wants a single source of truth enforced by the compiler, the string key API works against that goal. The repository also carries the usual caveats of a package that reaches a stable 3.x line: the latest listed release is v3.0.8 from 2025-07-24, the last push was on 2026-06-13, and the default branch is develop, so consumers pinning a version from pub.dev should check the CHANGELOG.md for changes between the pinned version and the current source before upgrading. The MIT licence keeps commercial use and modification straightforward, though the licence text itself governs and is not something to paraphrase.

Editorial conclusion

easy_localization fits Flutter teams that want to add or change a locale by dropping a file into assets/translations, and that accept runtime loading in exchange for it. It does not fit a team that needs the compiler to catch a missing or misspelled key before release, because lookup happens at runtime against a string path. Before adopting it, check that your translation keys are consistent across locales by hand or with a script, decide whether you will rely on the fallback locale for gaps, and confirm that the asset directory declared under flutter assets is bundled in release builds, since a missing asset surfaces as a missing key rather than a build error.

Frequently asked questions

How do I install easy_localization in a Flutter project?

Add the dependency to pubspec.yaml as easy_localization with the version you want, and declare the translation folder under flutter assets, for example assets/translations/. The README writes the version as a placeholder to be filled with the last version.

How do I add a new language to an easy_localization app?

Add a file named after the language or locale code, such as en.json or en-US.json, inside the assets/translations folder and add the matching Locale to supportedLocales. For iOS you also need to list the locale under CFBundleLocalizations in ios/Runner/Info.plist.

What happens when a translation key is missing?

The key is resolved against the fallback locale, and the package also offers an error widget for missing translations so a gap can be shown on screen instead of being silently filled. Which of the two you see depends on how you configure the widget.

Can easy_localization load translations from something other than asset files?

Yes. The default assetLoader is RootBundleAssetLoader, and the documentation points to a separate Easy Localization Loader project for JSON, CSV, HTTP, XML and YAML sources and for custom loader classes.

Official sources

  1. aissat/easy_localization on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/aissat-easy-localization.svg)](https://hysenlabs.com/projects/aissat-easy-localization)

Was this useful?