Flutter localization done right

Jimmy Aumard
Flutter Community
Published in
4 min readJun 7, 2021
Intl_flavors to manage Flutter translations of your apps.

Flutter officially supports localization, but it lacks flexibility and has some big limitations. Let’s see how we can improve this.

If you never saw the official documentation, please take a look at it:

Some limitations bother me with this method:

  • We can only have a single ARB file for the entire app,
  • We have to manually write ARB files,
  • It doesn’t support flavors,
  • Doesn’t work for pure Dart packages.

One ARB file

Why is it a problem? Simply if you want to manage your translations by feature, let’s say you want to defer an entire feature for Flutter web to have better loading times, why not include translations in those defers?

Manual ARB files

This is the thing that irritates me the most. Dart has the intl package that lets you write your messages easily. And the official method is just to let you deal with ARB format. That means that you have to remember how to write a plural message for example. For me personally, that’s not going to happen.

You currently have to write this in your ARB file:

"myCustomPlural": "{howMany,plural, =1{{howMany} minute ago}other{{howMany} minutes ago}}",
"@myCustomPlural": {
"description": "Description",
"type": "text",
"placeholders": {
"howMany": {}
}
},

Here is what you can write with intl (and IDE auto-completion):

String myCustomPlural(int howMany) => Intl.plural(howMany, one: '$howMany minute ago', other: '$howMany minutes ago', name: 'myCustomPlural', args: [howMany], desc: 'Description');

Which one do you prefer?

For sure if you make any errors Flutter will not be able to compile your ARB into Dart files. Flutter will let you know you’ve done something wrong, but only when you click that “run” button. What a waste of time. Flutter is supposed to be all about productivity right?

Flavor support

In some applications, you need to support multiple environments or multiple brands. For that Flutter allow you to create flavors. The official documentation is quite light as it only redirects to external articles.

Here is the link:

The problem is that there is no notion of just overriding branded terms, so you end up in duplicating everything. This might be because the official methodméthode doesn’t support multi ARB, so it’s complicated to support flavor too.

intl_flavors to the rescue

Based on those problems and limitations I have created a package called intl_flavors.

How this package is fixing the problems and limitations of the official method.

First, you don’t manage ARB files manually. You write a Dart class containing your Intl messages. If we take our previous example, that would be:

@GenerateIntl()
class Translations {
String myCustomPlural(int howMany) => Intl.plural(howMany, one: '$howMany minute ago', other: '$howMany minutes ago', name: 'myCustomPlural', args: [howMany], desc: 'Description');
}

The annotation GenerateIntl is what makes the magic. This annotation will tell intl_flavors that this is your translations. After that, all you need to do is to run your favorite build_runner command:

flutter pub run build_runner build

This will generate the ARB file containing your translations! You can now use your favorite translation service (mine is Localazy, you should try it) to translate your sentencesphrases into multiple languages.

Additionally, since that annotation can be applied to multiple classes, it allows you to generate multiple ARB files. This solves our final problem, feature-dependent translations.

This annotation let you customize few other things:

  • Supported locales,
  • Default locale,
  • Supported flavors,
  • Default Flavor.

By customizing this, all you have to do is pull the ARB filesfichiers from your translations service, and during the generation, intl_flavors will generate all the Dart files needed to support your locales and your flavors.

For example, we could have:

@GenerateIntl(locales: const {'fr'}, flavors: {'flavor1'})

The default locale is English. So I would have translations.arb, translations_fr.arb, translations_flavor1.arb and translations_flavor1_fr.arb on my file system. Once build_runner has finished, all I have to do is to set up the localizationsDelegates of the material app:

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
supportedLocales: TranslationsDelegate.supportedLocales,
localizationsDelegates: [
DefaultMaterialLocalizations.delegate,
TranslationsDelegate('flavor1'),
],
home: MyHomePage(),
);
}
}

To support flavors, I can pass the flavor name to the delegate, it does the job of picking the right translations under the hood.

Don’t hesitate to check the basic examples https://github.com/jaumard/intl_flavors/tree/master/packages/usage_example

Conclusion

With intl_flavors I’m able to:

  • manage multiple ARB files,
  • don’t deal with ARB manually as it’s generated from a Dart class,
  • support flavors automatically, the generated code takes the default translations except for the ones overridden in the ARB flavor,
  • support Dart and/or Flutter projects with the same code,
  • be integrated with build_runner like any other generator.

I’ll keep working on intl_flavors to add more things, but would love some feedback if you have any!

Cheers!

Follow Flutter Community on Twitter 🐥: https://twitter.com/FlutterComm

--

--