i18n in Flutter: the best alternative

In this post, I will explain how I managed to make my Flutter application translatable with the I18n_extension package, which in my opinion is the best alternative to achieve it.


Hello everyone! Today, I’m going to explain which library I chose to translate my Flutter app and why did I choose it.


First of all, I want to mention that the most popular alternatives that I found available in 2020 were the following ones:

  • i18n plugin: The Flutter i18n Plugin for IntelliJ IDEA was created by Razvan Lung. A plugin that simplifies i18n management in your project. You can find it perfectly explained in this article: https://dev.to/pedromassango/how-to-i18n-in-flutter-46km
  • i18n: dart package that translates automatically your labels in YAML files to Dart classes. I didn’t find it really useful in my app, because you need to access manually to the labels you are interested in for each language in your code. You can find documentation here https://pub.flutter-io.cn/packages/i18n
  • i18n_extension: personally, the option I found suits best for a Flutter app. It is about non-boilerplate translations. Besides, this package was mentioned by Google during the Dart 2.7 announcement.

So let me explain why and how I introduced the i18n_extension to my project.  


First, you need to add the next dependencies in your pubspeck.yaml file:

    sdk: flutter
  i18n_extension: any


The package “flutter_localizations” is also needed to track the device’s locale (the user’s preferred language).


After that, you need to go to your main.dart file and add the next:

import 'package:i18n_extension/i18n_widget.dart';                 
import 'package:flutter_localizations/flutter_localizations.dart';
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: [
      supportedLocales: [
        const Locale('en'),  // you can Add Country code if you want
        const Locale('es'), 
        const Locale('fr'),
        const Locale('pt'),
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      home: I18n(
          initialLocale: Locale("es"),
        child: MyStatefulWidget()

Now it’s time to start creating our translation files. To do so, we start by creating a dart file to hold them. This file can have any name, but I suggest you give it the same name as your widget and change the termination to .i18n.dart.

For example, if your widget is in file my_widget.dart, the translations could be in file my_widget.i18n.dart. Besides, you need to add the translations file in the same folder as your widget.


You must then remove the default import:

import 'package:i18n_extension/i18n_widget.dart';


And instead, import your own translation file:

import 'my_widget.i18n.dart';


Your translations file will be something like this:

import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String {
  static var _t = Translations("en_us") +
      "en_us": "Hello, how are you?",
      "pt_br": "Olá, como vai você?",
      "es": "¿Hola! Cómo estás?",
      "fr": "Salut, comment ca va?",
      "de": "Hallo, wie geht es dir?",
  String get i18n => localize(this, _t);          


BUT: if your Dart version is smaller than:

sdk: ">=2.6.0 <3.0.0"

  You might have issues creating your extension files!


You can, however, translate as many strings as you want, by simply adding more translation maps:

import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String {
    static var _t = Translations("en_us") +
          "en_us": "Hello, how are you?",
          "pt_br": "Olá, como vai você?",
        } +
          "en_us": "Hi",
          "pt_br": "Olá",
        } +
          "en_us": "Goodbye",
          "pt_br": "Adeus",
  String get i18n => localize(this, _t);


  String themselves are the translation keys

The locale you pass in the Translations("en_us") constructor is called the default locale. All translatable strings in the widget file should be in the language of that locale.

The strings themselves are used as keys when searching for translations to the other locales. For example, in the Text below, "Hello, how are you?" is both the translation to English and the key to use when searching for its other translations:

Text(“Hello, how are you?”.i18n)


Managing keys

Other translation packages ask you to define key identifiers for each translation and use those. For example, the above text key could be helloHowAreYou or simply greetings. And then you could access it like this: MyLocalizations.of(context).greetings.

However, having to define identifiers is not only a boring task, but it also forces you to navigate to the translation if you need to remember the exact text of the widget.

With i18n_extension you can simply type the text you want and that’s it. If some string is already translated and you later change it in the widget file, this will break the link between the key and the translation map. However, the package is smart enough to let you know when that happens, so it’s easy to fix.


  Last points

Finally, I think is important to mention that you can also provide the translations by locale/language, instead of by key. You can do that by using the Translations.byLocale() constructor.

static var _t = Translations.byLocale("en_us") +
      "en_us": {
        "Hi.": "Hi.",
        "Goodbye.": "Goodbye.",
      "es_es": {
        "Hi.": "Hola.",
        "Goodbye.": "Adiós.",


You can also combine different translations and use translation modifiers (for example, you can change a sentence and have different translations that depend on a number quantity. Instead of .i18n you can use .plural() and pass it a number. For example:

int numOfItems = 3;
return Text("You clicked the button %d times".plural(numOfItems));
static var _t = Translations("en_us") +
    "en_us": "You clicked the button %d times"
        .zero("You haven't clicked the button")
        .one("You clicked it once")
        .two("You clicked a couple times")
        .many("You clicked %d times")
    "pt_br": "Você clicou o botão %d vezes"
        .zero("Você não clicou no botão")
        .one("Você clicou uma única vez")
        .two("Você clicou um par de vezes")
        .many("Você clicou %d vezes")
String plural(int value) => localizePlural(value, this, _t);

You can use custom modifiers too.



This was a quick look into i18n_extension. I hope you found this article useful and you finally get to use this package, which, in my opinion, was not only the easiest one to use but the most powerful to internationalize my Flutter application. Head to the official package documentation for more details!

Thank you for taking the time to read my post!


Más Posts