Having one codebase for Android and iOS app sounds attractive. In the past, we didn’t have a great experience doing that. But lately, Flutter is attacking that problem in its own way. However, if you build an app for different markets, you will most likely need to support multiple languages for your end-users. Currently, there are several ways to achieve that. Below, you will find some of the most commonly used approaches regarding Flutter localization.
To support multiple languages in your Flutter application, you need to set it up first. Through this section, you will find out how to do that with the Flutter Intl plugin. This plugin will automatically generate localization code for you, make easy addition of new languages, extraction of text for translation, and similar.
All code samples used in this guide are available on the GitHub repo.
Install Flutter Intl plugin for IDE you’re using (Visual Studio Code or Android Studio).
In this guide we will explain how to do it in Android Studio. It should be fairly similar in VS Code, just follow the steps in the extension documentation.
Flutter Intl IDE plugin exceeded 200K installations on VS Code and Android Studio! 🎉
— Localizely (@localizely) April 13, 2021
Thanks for your feedback! #flutter #flutterdev
There is also a CLI tool intl_utils in case you need similar operations for Continuous Integration.
Go to Tools | Flutter Intl and run Initialize for the Project.
By default en
locale is added by auto-creating a file lib/l10n/intl_en.arb
.
Note: The plugin generates and maintains files inside lib/generated/
folder which you should not edit manually. But you should keep these files in your project repository.
Add dependency needed for localization to pubspec.yaml
file:
dependencies:
# Other dependencies...
flutter_localizations:
sdk: flutter
Set up your localizationsDelegates
and your supportedLocales
which will allow accessing the strings, localize Material's and Cupertino's widgets into ~78 languages (e.g. Material's date picker), and set up text directions within the app.
import 'package:flutter_localizations/flutter_localizations.dart';
import 'generated/l10n.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
title: 'Flutter Demo',
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
You probably want to localize your Flutter app into more than one locale.
You can add more locales by going to Tools | Flutter Intl and running Add locale command.
Your main ARB file by default is lib/l10n/intl_en.arb
. When you add new key-value pairs in it and save the file, those keys will be automatically available for auto-complete in your Dart code.
File content example:
{
"title": "Hello world!"
}
In essence, ARB file is a JSON file with some conventions.
The ARB file’s keys now correspond to methods from the S
class. For example:
Widget build(BuildContext context) {
return Text(
S.of(context).title
);
}
As an alternative way, you can just add a translation string in Dart code, and extract it to ARB files via intention action in Android Studio or via code actions in VS Code. That way you can add the same key to all ARB files.
As your app grows with the number of string keys and the number of different languages for end-users, it gets more complicated to maintain translations.
Here is how to streamline the localization workflow:
Once you sign up to Localizely, just go to My projects page and tap “+” button (or explore the Sample Project if you wish to get more familiar with Localizely features first). Give your project a name and set your main and other languages. You can change the main language later if needed.
Each project gets a unique ID (you can find it in My projects page), that is needed for the next step.
In your IDE go to Tools | Flutter Intl | Integrate with Localizely and run Connect command.
You will be asked for your personal API token which you can get from My Profile page, and project ID we mentioned in the previous step.
Enter the data and click connect.
Once connected, your personal API token will be stored inside the file <USER_HOME>/.localizely/credentials
, and project ID will be added to pubspec.yaml
file inside your project.
Check out plugin documentation for additional but optional config parameters inside pubspec.yaml
file.
During the development of the app, we constantly add new string keys that need to be translated later. A common practice is to add those new string keys only to the main locale during development.
Once planned features are implemented, you want to upload the main ARB file to Localizely, for your team to translate it.
In your IDE go to Tools | Flutter Intl | Integrate with Localizely and run Upload main ARB file command.
Note: Alternatively you can always add new string keys in Localizely platform first, and then download them for development. It depends on what fits into your workflow better.
Localizely has an editor on Translations page that reminds of Excel tables commonly used for translations management. Feel free to explore the Sample project or just take a look at Getting started guide to grasp the basic concept and options.
If you have previously invited your teammates to the Localizely project, you can assign them a task to translate newly added string keys.
The translation part is done.
In your IDE go to Tools | Flutter Intl | Integrate with Localizely and run Download ARB files command.
ARB files will be downloaded from Localizely platform and put in the proper place in your Flutter project.
Your Flutter app now has ready translations for the next release.
You are ready to do QA check and publish the app!
In case you are interested in instant updates of translations without the need for new releases, check out the Flutter Over-the-Air translation updates feature. It will let you correct typos, optimize text, and update translations on the fly.
Recently, Flutter introduced a new way of localization based on the gen_l10n
tool. This tool automatically generates localization files during the build phase. In the following chapters, you will find out how to use this new localization tool.
All code samples used in this guide are available on the GitHub repo.
First, let's add necessary dependencies into the pubspec.yaml
file.
dependencies:
# Other dependencies...
flutter_localizations:
sdk: flutter
intl: ^0.17.0
Next, run the following command from Terminal to download them:
flutter pub get
To enable automatic generation of localization files, update the flutter
section of the pubspec.yaml
file.
flutter:
generate: true
# Other config...
Create a new l10n.yaml
file in the root of the Flutter project. This file is going to hold the configuration for the gen_l10n
tool. You can find the full list of config options in this document. However, in this guide, we will use just a few of them:
arb-dir
- the path of the directory that contains the translation files.
template-arb-file
- the name of the template arb file that will be used as basis for generating the Dart localization files.
output-localization-file
- the name of the file for the output localization and localizations delegate classes.
Below, you can find the content of the l10n.yaml
file used in this guide.
arb-dir: lib/l10n
template-arb-file: intl_en.arb
output-localization-file: app_localizations.dart
Next, let's add the l10n
directory with three ARB files: intl_ar.arb
, intl_en.arb
, and intl_es.arb
. These files are going to hold translations for Arabic, English, and Spanish, respectively. Below, you can see the project structure after adding these files and their content.
FLUTTER_PROJECT
|-- ...
|-- android
|-- ios
|-- lib
| |-- l10n
| |-- intl_ar.arb
| |-- intl_en.arb
| |-- intl_es.arb
| |-- main.dart
|-- pubspec.yaml
|-- ...
The intl_ar.arb
file:
{
"helloWorld": "مرحبا بالعالم!"
}
The intl_en.arb
file:
{
"helloWorld": "Hello World!"
}
This intl_es.arb
file:
{
"helloWorld": "¡Hola Mundo!"
}
Note: The Flutter ARB file allows ICU syntax in messages. However, the gen_l10n
tool is still in the active development phase and does not yet fully support the entire ICU syntax. Reported issues: multiple/nested plurals and selects in messages, generator ignores plural forms: zero, one, and two.
According to the official documentation, for iOS apps, it is necessary to update the Info.plist
file with the list of supported locales. However, there are some indications that this could be automated soon. Until that happens, you should manually add the snippet shown below to support localization in iOS apps.
The ios/Runner/Info.plist
file:
<key>CFBundleLocalizations</key>
<array>
<string>ar</string>
<string>en</string>
<string>es</string>
</array>
To trigger the generation of the localization files, you need to run the app. After that, you will be able to see the generated code under the .dart_tool
folder.
FLUTTER_PROJECT
|-- .dart_tool
| |-- ...
| |-- flutter_gen
| |-- gen_l10n
| |-- app_localizations.dart
| |-- app_localizations_ar.dart
| |-- app_localizations_en.dart
| |-- app_localizations_es.dart
| |-- ...
|-- android
|-- ios
|-- lib
|-- ...
Note: Each time you run, hot-reload, hot-restart, or run flutter pub get
command, you will trigger code generation.
By default, the gen_l10n
tool generates localization files as a synthetic package. Therefore these files are not checked into the version control system (i.e. Git). However, if for some reason you want to track this code with the version control system, you will need to update your l10n.yaml
file with synthetic-package
and output-dir
config params.
The l10n.yaml
file:
# Other config...
synthetic-package: false
output-dir: lib/l10n
Next, update the MaterialApp
widget with the localizationsDelegates
and supportedLocales
props. For that, import generated app_localizations.dart
file and pass needed values.
The AppLocalizations.localizationsDelegates
represents the list of:
GlobalMaterialLocalizations.delegate
- provides localized messages for the Material widgets,GlobalCupertinoLocalizations.delegate
- provides localized messages for Cupertino widgets,GlobalWidgetsLocalizations.delegate
- provides text direction for widgets.These delegates allow us to use different languages throughout the app without much hassle. Moreover, they localize Material's and Cupertino's widgets into ~78 languages (e.g. Material's date picker) and set up text directions within the app.
The AppLocalizations.supportedLocales
represents the list of supported locales.
The main.dart
file:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
// ...
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
// ...
);
}
}
Further, to see how everything works, paste the below-shown snippet somewhere in the code.
Text(AppLocalizations.of(context)!.helloWorld)
Finally, hot-reload the app.
If you followed everything from the beginning, you should be able to see the translated message. Note that whenever you change the language of the device to Arabic, English, or Spanish, the mentioned message will be updated.
Although most languages are LTR (Left-to-Right), we should be aware that there are also RTL (Right-to-Left) languages. In this guide, we added one RTL language, Arabic. Whenever you open the app in this language, you should notice your layout changes. Generally, Flutter updates the text direction within the app to correspond to the selected locale. In most cases, you will not have to adjust anything. However, if your layout is created only for LTR languages or uses some hard-coded values, there is a possibility that you may break it. So be aware that adding the RTL language to your app might also require some changes in your layout.
To get the selected locale of the widget tree, use the below-shown snippet.
Locale selectedLocale = Localizations.localeOf(context);
In some rare cases, you might want to override the locale of one part of the widget tree. For that purpose, you should use the Localizations.override
factory constructor. Below, you can see an example where the helloWorld
message is shown regularly and with the overridden locale.
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(AppLocalizations.of(context)!.helloWorld),
Localizations.override(
context: context,
locale: const Locale('es'),
// Using a Builder here to get the correct BuildContext.
child: Builder(
builder: (BuildContext context) {
return Text(AppLocalizations.of(context)!.helloWorld);
},
),
),
],
);
}
So far, we've seen how Flutter detects the device language (user's preferred language) and adjusts the app according to it. Also, we've seen how to override the language of some parts of the app. But what if we need to support a manual language switcher in the app. Is it difficult? Generally, it is not. You just need to pass the selected locale as a locale
prop in the MaterialApp
widget. For more details, check this example.
Easy to manage a couple of strings in two languages? Sure. But as you add more features and expand to more markets, your app will grow to thousands of strings in a dozen of languages. Here’s how to automate your text string localization:
Once you sign up to Localizely, just go to My projects page and tap “+
” button (or explore the Sample Project if you wish to get more familiar with Localizely features first). Give your project a name and set your main and other languages. You can change the main language later if needed.
Each project gets a unique ID (you can find it in My projects page), that is needed when using API.
Import your main ARB file to Localizely. Go to Upload page, select the file, and confirm. Alternatively, you can start by adding string keys in Localizely first.
Localization work is a team effort. Switch to the Contributors section by clicking the icon in the side menu and start adding teammates. Any user can be granted admin privileges, i.e. the same rights on the project as you. For non-admin contributors, you can specify per-language access to the project specifying some languages as reference (read-only) or contributable (read and update). Only admins have access to string key changes, importing, exporting, settings, etc.
Localizely has an editor on Translations page that reminds of Excel tables commonly used for translations management. Feel free to explore the Sample project or just take a look at Getting started guide to grasp the basic concept and options.
The translation part is done. How to get the localization data out of Localizely and make it usable in your Flutter app?
There are 2 options:
Click Download icon in your project side menu and select Flutter ARB as the exporting format. Click Download to get the file. Then move downloaded .arb file into your project replacing the existing localization, that’s all.
Depending on your project deployment setup, you may want to use Localizely API – it gives you a simple way to automatically generate and download localization files.
Flutter has good documentation on how to get started with a sample app, so we will not go into that here. We will describe how to introduce localization to your sample app. You can use Android Studio or Visual Studio Code as IDE, it is the same.
Start by defining these localization dependencies in your pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
flutter_localizations: # added
sdk: flutter # added
dev_dependencies:
flutter_test:
sdk: flutter
intl_translation: # added
Install packages by running following command from Terminal:
flutter pub get
We can do this by creating AppLocalizations
and AppLocalizationsDelegate
classes in a separate lib/localizations.dart
file.
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'l10n/messages_all.dart';
class AppLocalizations {
AppLocalizations();
static const AppLocalizationsDelegate delegate = AppLocalizationsDelegate();
static Future<AppLocalizations> load(Locale locale) {
final String name = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
final String localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
Intl.defaultLocale = localeName;
return AppLocalizations();
});
}
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
// Your keys defined below
String get title {
return Intl.message(
'Hello World',
name: 'title',
desc: 'Title for the Localized application',
);
}
String welcome(String name) {
return Intl.message(
'Welcome {name}',
name: 'welcome',
desc: 'Welcome message',
args: [name],
);
}
}
class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
const AppLocalizationsDelegate();
// Override with your list of supported language codes
List<Locale> get supportedLocales {
return const <Locale>[
Locale('en', ''),
Locale('de', ''),
];
}
@override
bool isSupported(Locale locale) => _isSupported(locale);
@override
Future<AppLocalizations> load(Locale locale) => AppLocalizations.load(locale);
@override
bool shouldReload(AppLocalizationsDelegate old) => false;
bool _isSupported(Locale locale) {
if (locale != null) {
for (Locale supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale.languageCode) {
return true;
}
}
}
return false;
}
}
At the end of AppLocalizations
class we define all string keys we will use inside our app.AppLocalizationsDelegate
class has a list of supportedLocales
. We should put all locales we support for end-users into that list.
Now, that we have defined 2 string keys in AppLocalizations
class, we can simply use them in our widgets.
In the following example, we use them to print the title on the top of the app page, and a simple welcome message with a placeholder.
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'localizations.dart'; // your previous file
class DemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).title),
),
body: Text(AppLocalizations.of(context).welcome("John")));
}
}
class Demo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateTitle: (BuildContext context) =>
AppLocalizations.of(context).title,
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: AppLocalizations.delegate.supportedLocales,
// Note: AppLocalizations.of()
// will only find the app's Localizations widget if its
// context is a child of the app.
home: DemoApp());
}
}
void main() {
runApp(Demo());
}
When you want programmatically to change the current locale in your app, you can do it in the following way:
AppLocalizations.load(Locale('en', ''));
The code is now ready, but we miss localization files for languages we support. Flutter officially supports *.arb localization files.
Flutter offers a CLI tool that generates a default intl_messages.arb
file by scanning your AppLocalizations
class.
Create lib/l10n
folder and run the following command from the project root:
flutter pub pub run intl_translation:extract_to_arb --output-dir=lib/l10n lib/localizations.dart
That arb file contains all key-value pairs we use in our Widgets.
Initially, we can copy the content of that file and put it into intl_de.arb
file in the same directory, and just translate the values into the German language.
Once we are completed with translations, we should run the following command in order to generate *.dart
files which our app will directly load:
flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/localizations.dart lib/l10n/intl_*.arb
You can now build and run your application.
Easy to manage a couple of strings in two languages? Sure. But as you add more features and expand to more markets, your app will grow to thousands of strings in a dozen of languages. Here’s how to automate your text string localization:
Once you sign up to Localizely, just go to My projects page and tap “+
” button (or explore the Sample Project if you wish to get more familiar with Localizely features first). Give your project a name and set your main and other languages. You can change the main language later if needed.
Each project gets a unique ID (you can find it in My projects page), that is needed when using API.
Import your main intl_messages.arb
file to Localizely. Go to Upload page, select the file, and confirm. Alternatively, you can start by adding string keys in Localizely first.
Localization work is a team effort. Switch to the Contributors section by clicking the icon in the side menu and start adding teammates. Any user can be granted admin privileges, i.e. the same rights on the project as you. For non-admin contributors, you can specify per-language access to the project specifying some languages as reference (read-only) or contributable (read and update). Only admins have access to string key changes, importing, exporting, settings, etc.
Localizely has an editor on Translations page that reminds of Excel tables commonly used for translations management. Feel free to explore the Sample project or just take a look at Getting started guide to grasp the basic concept and options.
The translation part is done. How to get the localization data out of Localizely and make it usable in your Flutter app?
There are 2 options:
Click Download icon in your project side menu and select Flutter ARB as the exporting format. Click Download to get the file. Then move downloaded .arb file into your project replacing the existing localization, that’s all.
Depending on your project deployment setup, you may want to use Localizely API – it gives you a simple way to automatically generate and download localization files.
This workflow can be simplified further by using Flutter Intl plugin explained in this guide.
Like this article? Share it!
Zoran is a Software Engineer at Localizely. His primary interest is web development, but he also has a solid background in other technologies. For the last few years, he has been involved in the development of software localization tools.
Subscribe to the Localizely blog newsletter for quality product content in your inbox.
Related