Localising Flutter applications and automating the localisation process
Developing a small application or PoC in one language is acceptable, but as soon as you release an app to the public you might want to consider localising it first. I live in Belgium, a country best known for its chocolate, waffles, fries and beer. We have three official languages: Dutch, French and German. We also have English as an unofficial language. If I want to publish an app and I want as many Belgians as possible to be able to use it, it’ll definitely need to support these languages.
The more languages and locales your application supports, the biggeryour target audience will be.
In this article I will guide you through localising your Flutter application using .arb (Application Resource Bundle) files and the Intl package. I will also show you how you can automatically provide your app with the latest translations using the Loco tool. I’ve written a shell script that can be run locally or used by a CI/CD tool of choice. I’ll be using Codemagic because it’s easy to set up and visualise.
Adding the dependencies to the project
Before you can localise your Flutter app, you’ll first need to add some dependencies to the pubspec.yaml file of your project.
Localisation in the MaterialApp
Most of the setup for the localisation happens in the MaterialApp, which is the heart of your Flutter app. It doesn’t necessarily have to be a MaterialApp though, it can also be a CupertinoApp or a WidgetsApp. This is the place where the supported locales are defined and is decided which locale is going to be used in the app. It’s also where we’ll add our own localisations delegate, which is where the rest of the magic happens.
Creating the AppLocalizations class and the AppLocalizationsDelegate
The AppLocalizations class contains the logic to load the messages (= translations) of a given locale. It’s also where you’ll define all Intl messages that will be used in the app. All messages should be in the same language, the language of the ‘source locale’ of your project. In this case it’s Belgian English.
I’ve created two Intl messages: one for the app title and one for a ‘hello world’ message that we’ll use later in the UI.
The AppLocalizationsDelegate is the glue between the AppLocalizations class and the MaterialApp. The load function returns an AppLocalizations object as it contains all localised messages. The isSupported function can be used to check if a certain locale (or in this case language code) is supported.
If you copy/paste this code you’ll get an error on the initializeMessages method, as it’s not defined yet. This method will be generated later by the Intl package. Before you can start providing translations to your Flutter app, you first need to create a Loco project.
Creating the Loco project
Head over to Loco and create an account if you don’t already have one. Loco has a generous free plan which allows you to create two projects. Create a new Loco project and make sure to select the same source locale you’ve used for your Intl messages.
Next, head over to ‘Developer Tools’ and click on ‘API Keys’. This will open up a dialog in which you can create a ‘Full access key’ for your project. Make sure to save this key somewhere, as it’s impossible to retrieve it later. In case you lose this key, you’ll have to generate a new one.
How Flutter works together with Loco
Your Flutter app will interact with Loco in two ways. The Intl messages you created in the AppLocalizations class will be imported into Loco. Everytime you add or update these translations your Loco project will get an update as well. New assets will be created in your source locale or existing assets will get a new value. The second way you use Loco is by downloading all translations for all locales in .arb format and then turning these files into .dart files.
It doesn’t have to be the job of the developer to provider the translations for the app, it can also be done by a product owner or a client for example.
In your Loco project you can choose additional locales you want to support, in my case Belgian Dutch and Belgian French. Translating messages for a new locale from your source locale can be done by a very user-friendly GUI. You don’t need to be a technical person to add or edit translations.
Automating the localisation process
Importing messages into Loco and exporting translations from Loco can be done manually, but it’s a tedious process. These are the steps that need to be taken for the entire localisation process:
- Generate .arb files from the Intl messages defined in the AppLocalizations class.
- Import those .arb files into the Loco project to add/update the translations for the source locale.
- Fetch the latest translations for all locales in .arb format from the Loco project.
- Generate .dart files from these translations.
It’s possible to automate this process with a simple shell script I’ve provided below. The only thing you need to do is provide a value for all variables: your Loco API key, the path of the AppLocalizations class and the desired output path for the translations.
It’s possible to run this script locally but you can also integrate it into your CI/CD tool of choice. I’ve chosen to use Codemagic because it’s easy to set up and use. Codemagic allows you to execute shell scripts before and after every phase of a build. It’s important to execute this script before the build phase though. I’ve decide to run the script before the test phase.
Using a CI/CD tool to fetch translations automatically will make sure users always have the most recent translations.
The reason it’s so useful to integrate this script into a CI/CD tool is that with every build of the application the latest translations will be fetched automatically. A developer might forget to fetch the latest translations before releasing a new version of the Flutter app to the stores and it’s possible that another person has added or updated translations on Loco.
Using localised messages in the UI
In the example below you can see how to get a localised message from the AppLocalizations class and use it in a Text widget. Try changing the language of your device without closing the app. When you open the app again, the widget will be rerendered automatically and display the message in the language you’ve selected (as long as it’s supported, obviously).
Using localised messages without context
Using localised messages without context is possible but it’s a bit hacky in my opinion. There’s currently no other way to do it though.
It can sometimes be useful to get a localised message without using context, in a BLoC or a ViewModel for example.
I’ve added a static AppLocalizations object in the AppLocalizations class. It gets its value from the private constructor, which is used in the load method. As you can see in the example below, it’s now possible to use localised messages without context.
Localising the app title
It’s also possible to localise the name of your application by using the onGenerateTitle callback of the MaterialApp at the root of your project. Make sure you’re not using the ‘title’ property of the MaterialApp.
Conclusion
This approach makes localising your Flutter apps a lot easier. Developers create or adjust Intl messages in the source locale in the AppLocalizations class. Anyone can provide translations for those messages on Loco, it doesn’t have to be the developer. Using the provided script and a CI/CD tool of choice will make sure the Flutter app and the Loco project are always synchronised. This allows the users of the Flutter app to always have the most recent translations.