Flutter Internationalization : the easy way with flappy_translator

Thomas Ecalle
Smart&Soft Blog
Published in
5 min readAug 23, 2019

--

A lot of flags of the world
Make your app available for everyone

Being able to handle internationalization (i18n) in an app is often mandatory.
Indeed, all the static strings of you app should be adapted to your users language in order for them to have the best experience possible.

While handling i18n in other technologies may be quite simple (like in Android with the strings XML file), doing it in Flutter is not that easy.

Requirements

Let’s say that we want to create a very simple application with 3 strings and accessible in 2 languages (English and French).
The strings would be:

  • the app title (a simple string translated in both languages),
  • a description (a parameterized string also translated in both languages),
  • an “ok” label that doesn’t need to be translated.

Also:

  • our strings need to be stored in a human-readable and easy-to-use-or-update format for non-developers, this feature is really important when working on a real project, it enables developers to keep working on the code while other people can assume the role of writing and translating the string resources,
  • integrating strings in one’s code should be simple, clear, and prevent runtime errors (it would need typed values to be able to have autocompletion),
  • we should be able to parameterize the string while keeping it typed and clear.

I - The official way

Here is the official way to handle i18n in Flutter:

This article deals about several ways to handle i18n and is really well explained.
I recommend you to read it if you do not feel really comfortable with these notions.

Why it does not fulfill our needs

There is no way to handle parameterized strings nor to store strings in a good format for non-developers.

II - The other ways

There are a lot of ways to handle i18n and I assume that it is somehow a matter of taste.

However, some of them seem to be more elegant and easy-to-use than others.
A really good implementation and way to think might be the one from Didier Boelens:

https://www.didierboelens.com/fr/2018/04/internationalisation---r%C3%A9alisez-une-application-flutter-multilingue/

I highly recommend Didier Boelens articles as they are really well explained and interesting

This implementation is really interesting as it enables us to store string resources in a JSON format.

Why it does not fulfill our needs

While this implementation is really great and “non-developers compliant”, it has its drawbacks.

The fact that you have to use your strings like this in your app is a problem for us:

Translations.of(context).text(‘main_title’)

Indeed, because we have to use a string as identifier, we cannot have any compile-time guarantee whether its string exists or not, leading to runtime errors. Also, one cannot use IDE autocompletion features.

Furthermore, there is no _out of the shelf_ way to handle parametrized strings.

We would have to create separate functions in order to handle different parameters for every string, etc. Quite a lot of work!

The flappy_translator’s way

At Equinoa, we work on the shoulders of a giant, so our aim was not to reinvent the wheel. We base our new solution on Dider Boelens’s awesome work, by adding a bit of magic to remove most of boilerplate code.

The idea is to be able to convert a CSV file containing all the translations in an auto generated Dart class taking advantage of the previous examples and guaranteeing compile-time safety and clear syntax.

How to use it

https://pub.dev/packages/flappy_translator

1. Let’s say that this is our CSV file

CSV is a great format as it enables anybody to update / add / delete strings and is really simple to use for non-developers.

2. Export it as a comma-separated CSV file

keys,fr,en
appTitle,Un super titre,An awesome title
description,Une descritpion avec une variable : %name$s,A descritpion with a variable : %name$s
ok,ok,

3. Add the following dependencies

dependencies:
flutter_localizations:
sdk: flutter

dev_dependencies:
flappy_translator:

4. Run Package

flutter pub get
flutter pub run flappy_translator test.csv path/to/destination

Here, you give 2 parameters to flappy_translator :

  • your CSV file (here test.csv) -> mandatory,
  • the path where you want the generated class to be created -> optional (the class will be create in current directory if not provided).

These steps will generate a file named i18n.dart

5. Add the generated I18nDelegate in your localizationsDelegates

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
const I18nDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''),
const Locale('fr', ''),
],
home: Home(),
);
}
}

6. VOILÀ! Now you may enjoy the I18n class :)

class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
children: <Widget>[
Text(I18n.of(context).appTitle),
Text(I18n.of(context).description(age: 25,)),
Text(I18n.of(context).ok),
],
),
),
),
);
}
}

As you can see, this solution fulfills our needs:

  • strings are handled by getters and parameterized functions,
    it enables us to use autocompletion and prevents runtime errors,
  • the parameters in a parameterized string are handled in an optional way in order to be really readable and easy to use for the developer,
  • all strings are stored in a CSV file which means that every non-developer team’s member can update them,
  • modifying the strings is now a matter of micro-seconds!
    Indeed, you will only have to launch again the process and that’s it, a ready to use new class will be generated in place of the previous one :)

Features and requirements

Currently, the only requirements to respect are:

  • use a comma-separated CSV file,
  • each column represents a language in the CSV (Use ISO identifiers as column headers like “fr” for French),
  • the first language column — which is the default and fallback language — must have all its translations filled,
  • the first CSV column is the the keys’ one, the generated getters and functions will respect the same syntax,
  • the variables placeholders in String must correspond to this template:
    %age$d (“d” standing for an int)
    %myVariableName$s (“s” standing for a string)
  • the keys must not be part of the reserved words of the Dart language:
    https://medium.com/jay-tillu/keywords-in-dart-dd111fbbfe57.

Features are:

  • only the first language must be filled, others will be automatically filled with it if needed,
  • strings may be parameterized,
  • there is no effort needed from the developer!

All you need to do is to launch the process and then call every getters or functions of the generated class.
That is a great time saver!

What’s next?

Thank you all for reading this article!
We hope that this package will be as useful for you that it is for us.

We would love to have feedback from the community in order to improve ourselves and to update this tool with a lot of new features :)

Do not hesitate to star the repository if you like it ;)

https://github.com/smartnsoft/FlappyTranslator

Made with ❤ at Equinoa in Paris, 🇫🇷

--

--