Introduction

Supporting multiple languages in Flutter involves three primary concerns:

  • Separation of text and code: avoids string literals in widgets.
  • Complex language rules: plurals, gender variations, and formatting.
  • Update agility: pushing copy fixes without full rebuilds.

Why Choose YAML Over ARB/JSON?

YAML offers:

  • Clean, minimal syntax (indentation-based).
  • Multi-line strings and inline comments.
  • Natural nesting for logical domains.

Compare:

YAML Example:
YAML Example
ARB/JSON Equivalent:
ARB/JSON equivalent

Three Localization Approaches were considered

  1. Official: flutter_localizations + intl
  2. Dynamic: easy_localization
  3. Typed‑YAML: easiest_localization

Below, each section includes setup highlights, strengths, and limitations.


1. Official: flutter_localizations + intl

Note: Each library is easily discoverable on pub.dev by its name.

Strengths:

  • Officially supported by Flutter core.
  • Generated classes with typed getters and parameters.

Limitations:

  • ARB/JSON has no comments and only flat keys.
  • ICU MessageFormat syntax is verbose for plurals/gender.
  • Every lookup requires BuildContext.
  • Built-in approach does not support over-the-air content updates without custom delegates.

Example: Configuring MaterialApp with localization delegates


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle,
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      home: HomePage(),
    );
  }
}

Example: Accessing localized strings
final title = AppLocalizations.of(context)!.appTitle;
final greeting = AppLocalizations.of(context)!.greeting(userName: 'Alice');

Accessing localized strings


2. Dynamic: easy_localization

Note: Each library is easily discoverable on pub.dev by its name.

Strengths:

  • Rapid setup with minimal boilerplate.
  • Supports JSON or YAML files.
  • Built-in HTTP loader for remote translation files.

Limitations:

  • Lookups are string-based, causing runtime errors on typos.
  • No compile-time key checks or autocompletion.
  • Locale variants require full file duplication (e.g., en_CA replicates en).

Example: Wrapping your app in EasyLocalization

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      home: HomePage(),
    );
  }
}

Example: Accessing localized strings
Text('welcome'.tr());
Text('greeting'.tr(args: ['Alice']));


3. Option we picked: easiest_localization

Note: Each library is easily discoverable on pub.dev by its name.

Strengths:

  • Full type safety: generated getters/functions match YAML placeholders.
  • No dependency on BuildContext.
  • Supports runtime merging of remote JSON/YAML with local defaults.
  • Clean, nested files with comment support.

Limitations:

  • Smaller community and ecosystem than official packages.

Setup highlights:

  • Create nested YAML under assets/i18n/en.yaml:
    home:
      greeting: "Hello, ${user}!"
      taskCount:
        zero: "No tasks"
        one: "One task"
        other: "${count} tasks"
    
  • Generate code:
    dart run easiest_localization --watch
    
  • Integrate in MaterialApp:
    import 'package:localization_gen/localization_gen.dart' as el;
    
    return MaterialApp(
      localizationsDelegates: el.localizationsDelegates,
      supportedLocales: el.supportedLocales,
    );
    

Example: Using generated APIs

Text(el.home.greeting(user: 'Alice'));
Text(el.home.taskCount(count: items.length));

Using generated APIs


Key Takeaways

To sum up, here are the advantages we gain from using easiest_localization:

  • Static type safety (if the code compiles without errors, we know the localization content is correct)
  • Treating content as code (extremely developer‑friendly, fast to work with, full IDE autocompletion)
  • Easy, secure integration with third‑party localization services such as Crowdin (our case)
  • Quick updates and additions of content with full support for all localization features—pluralization, arguments, and more
  • Future‑proofing for real‑time content updates without needing to republish the app to the stores, while retaining all of the benefits above


Mike Alfa

I’ve built Flutter apps since 2018 and quickly learned that even a single-language product benefits from proper localization—cleaner code, correct plurals, and easier copy updates. After wrestling with the standard ARB/Intl workflow, I explored alternative libraries and file formats. Below are the practical lessons (and pitfalls) I uncovered; watch for a follow-up post with deeper dives.

Author posts
  © 2022 Collectors Holdings, Inc. All rights reserved.  |   Legal & Privacy

Privacy Preference Center