Localizing your app with Flutter's new gen_l10n tool

I'm recently getting into Flutter so I'm still figuring out some common practises. I was surprised to find that the recommended approach for internationalization was quite cumbersome and laborious.

Fortunately, the Flutter team is working on a simplified i18n process that can already be used today and in this post I'll show you how!

Proposed new i18n process

The proposed workflow for this new i18n process is as follows:

  1. Start with handwritten .arb files. One of these files will contain the standard per-message metadata required by the intl package.

  2. A new tool (gen_l10n) will be used to generate a single class that contains one method per message.

  3. Message lookup is based on the inherited locale, using the current BuildContext:

    MyLocalizations.of(context).myMessage

Compared to some other solutions available on https://pub.dev this has a few advantages in my opinion:

In the following section I'll describe how to adopt this workflow for an existing project.

Adopt gen_l10n for an existing project

To get started we need a few new dependencies.

Add gen_l10n dependencies

The code generated by gen_l10n depends on the intl and flutter_localizations package, so add these to your dependencies:

# pubspec.yaml
dependencies:
# ... other dependencies ...
flutter_localizations:
sdk: flutter
intl: ^0.16.0

The gen_l10n tool itself depends on intl_translation, so add this as a development dependency:

# pubspec.yaml
dev_dependencies:
# ... other dev dependencies ...
intl_translation: ^0.17.9

Define translations

Ok, so now we have all dependencies we need, let's start by defining some localized messages.

Create a directory in your project to store your localized messages. This can be any directory you like, I'm using lib/i18n:

mkdir lib/i18n

In this directory create a file called messages_en_US.arb. The name messages here can be anything you want, but the last part of the file name should refer to the locale of the messages inside.

As you can see this file is an ARB file, an Application Resource Bundle. It's a localization resource format designed by Google. You can read the specification here.

Now to define localised messages we'll start by adding the messages we want to localize to the messages_en_US.arb file we created earlier. I won't dive into everything the ARB format supports, but it's a pretty versatile format so be sure to read the specification I linked above!

{
"greeting": "Hello, world!",
"@greeting": {
"type": "text",
"description": "A friendly greeting."
},

"newMessages": "{newMessages, plural, =0 {No new messages} =1 {One new message} other {{newMessages} new messages}}",
"@newMessages": {
"type": "text",
"description": "Number of new messages in inbox.",
"placeholders": {
"newMessages": {}
}
}
}

As you can see for every message we want to localize we add both a key and a meta key to the file. The key is how you will refer to the localized message from your code later and the meta key is that same key prefixed with @. These meta keys only need to be added in one of the locales; the locale you will use as a template.

In the example above we have both a simple message greeting and a more complex, pluralized, message that changes depending on a piece of context'; the number of new messages in this case.

Now for every other language we want to support we need to create matching files with the desired locale in the lib/i18n directory we created earlier. For example I'm going to translate these messages in Dutch so I'll add a file named messages_nl_NL.arb with the following contents:

{
"greeting": "Hallo, wereld!",
"newMessages": "{newMessages, plural, =0 {Geen nieuwe berichten} =1 {Één nieuw bericht} other {{newMessages} nieuwe berichten}}"
}

As you can see in this case the file only contains the keys themselves and no metadata.

Generate localization classes

With everything prepared we can now use the gen_l10n tool to generate the Dart classes we'll use to access these localized messages in our code.

As the gen_l10n is still experimental it's not yet distributed as a package on the package registry. Instead it's bundled with the Flutter SDK itself. The tool is located in the Flutter SDK at dev/tools/localization/gen_l10n.dart. In my case the Flutter SDK is installed in /Users/Pascal/.asdf/installs/flutter/1.12.13+hotfix.8-stable/ so the full path to the tool is /Users/Pascal/.asdf/installs/flutter/1.12.13+hotfix.8-stable/dev/tools/localization/gen_l10n.dart.

From your terminal, run the gen_l10n tool from the root of your Flutter project as follows:

flutter pub run "/path/to/flutter/sdk/dev/tools/localization/gen_l10n.dart" --arb-dir lib/i18n --template-arb-file=messages_en_US.arb --output-localization-file=translations.dart --output-class=Translations

(Be sure to replace /path/to/flutter/sdk above with the actual path to your Flutter SDK!)

This will generate a couple of files in lib/i18n:

lib/i18n
├── messages_all.dart
├── messages_en_US.dart
├── messages_nl_NL.dart
├── messages_en_US.arb
├── messages_nl_NL.arb
└── translations.dart

All these files should be checked into version control. The only files you manually edit, however, are the ARB files. Don't touch the generated Dart files :-)

Make localized messages available via BuildContext

To make Flutter aware of our localized messages we need to configure the localizationsDelegates and supportedLocales properties on the MaterialApp widget.

In your lib/main.dart file import the generated translations file:

import 'package:hello_world/i18n/translations.dart';

And configure the MaterialApp widget to pick up your translations:

MaterialApp(
// ... other properties ...
localizationsDelegates: Translations.localizationsDelegates,
supportedLocales: Translations.supportedLocales
)

Use localized messages

With all these out of the way, we can now actually use our localized messages.

From any widget in the widget tree below MaterialApp you can now access your localized messages.

Start by importing the generated translations.dart file:

import 'package:hello_world/i18n/translations.dart';

And use the messages defined in the ARB files:

class MyLocalizedWidget extends StatelessWidget {

Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(Translations.of(context).greeting), // "greeting"
Text(Translations.of(context).newMessages(10)) // "newMessages"
],
);
}
}

The compiler will check you're using your messages correctly and the IDE will help you with great autocompletion:

autocomplete.

Happy i18n-ing! 🎉