Zoran Luledzija
Zoran Luledzija
November 09, 2023
19 min read
November 09, 2023
19 min read

Flutter localization: step-by-step

Flutter localization: step-by-step

The idea of maintaining a single codebase for both Android and iOS applications is quite appealing. In the past, our efforts to realize this concept met with limited success. Nonetheless, Flutter is confronting this challenge with an innovative approach. Additionally, its adaptability extends beyond mobile platforms, enabling the development of applications for the web, desktop, and even embedded systems.

The benefits of crafting multiple applications from a single codebase cannot be overstated. But how does this translate when catering to international markets that require multilingual support? Initially, Flutter's tools for localization were fairly rudimentary, but they have evolved significantly since then. Today, a plethora of robust localization solutions are available. In the upcoming chapters, we'll explore some of the most popular and effective tools for localization in depth.

Part 1: Setting up your Flutter app

Flutter recently introduced a new localization method using the gen_l10n tool, seamlessly integrating into the development workflow. It automatically generates all necessary localization files, allowing you to focus solely on crafting localization messages and their usage in the app. This automation of the typically time-consuming tasks associated with localization not only propels development speed but also significantly enhances the developer experience. In the upcoming chapters, we'll guide you on how to effectively leverage this powerful localization tool.

All code samples used in this section are available on the GitHub repo.

Step 1: Add dependencies

First, let's add the necessary dependencies into the pubspec.yaml file.

dependencies:
  # Other dependencies...
  flutter_localizations:
    sdk: flutter
  intl: any

Next, run the following command from Terminal to download them:

flutter pub get

Step 2: Enable generation of localization files

To enable automatic generation of localization files, update the flutter section of the pubspec.yaml file.

flutter:
  generate: true
  # Other config...

Step 3: Configure localization tool

Create a new l10n.yaml file in the root of the Flutter project. This file will hold the configuration for the gen_l10n tool. You can find the full list of configuration 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

Step 4: Add translation files

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!"
}

More info about ARB localization files can be found in a dedicated guide.

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 include: multiple/nested plurals and selects in messages; the generator ignores plural forms: zero, one, and two.

Localization in the iOS apps

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 automation of this process may be possible in the future. Until then, manually add the following snippet 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>

Step 5: Run the app to trigger code generation

To trigger the generation of the localization files, you need to run your application. After that, you will be able to see the generated code in 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 flutter pub get or flutter run command, you will trigger code generation. For generating the same files independently of the application, you can use the flutter gen-l10n command as an alternative.

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 parameters.

The l10n.yaml file:

# Other config...
synthetic-package: false
output-dir: lib/l10n

Step 6: Update the app

The next step involves updating the MaterialApp widget with the localizationsDelegates and supportedLocales properties. So, let's import the generated app_localizations.dart file and set the needed values.

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,
      // ...
    );
  }
}

The AppLocalizations.localizationsDelegates represents the list of:

  • generated localizations delegate – provides localized messages from ARB files.
  • 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. They also translate the interface of both Material and Cupertino widgets into around 113 languages (for instance, the Material date picker) and configure text directions within the app.

The AppLocalizations.supportedLocales represents the list of supported locales.

Finally, paste the snippet shown below somewhere in the code and hot-reload the app to see how everything works.

Text(AppLocalizations.of(context)!.helloWorld)

If you have followed all the steps 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 message will be updated.

Text direction: Left-to-Right and Right-to-Left

Although most languages are written Left-to-Right (LTR), it is important to be aware that there are also Right-to-Left (RTL) languages. In this guide, we have included Arabic, which is an RTL language. Whenever the app is opened in Arabic, the layout should change accordingly. Generally, the Flutter framework updates the text direction within the app to correspond to the selected locale. In most cases, no adjustments are necessary. However, if the layout is designed exclusively for LTR languages or uses some hard-coded values, there is a possibility it may not display correctly. Be aware that adding an RTL language like Arabic to your app might also necessitate changes to your layout.

Escaping syntax

Typically, the content of localization messages is straightforward. Nevertheless, on occasion, there is a need to incorporate messages with special characters, such as curly braces { and }. To prevent these tokens from being parsed, it is necessary to enable the use-escaping flag within the l10n.yaml file.

The l10n.yaml file:

use-escaping: true

Once escaping is enabled, utilize single quotes to escape special characters in your localization messages.

{
  "escapingExample": "In math, '{1, 2, 3}' denotes a set."
}

Avoid null checking

By default, getters in the generated localizations class are nullable. To eliminate the need for repeated null checks, you can turn off the nullable-getter option in the l10n.yaml file.

The l10n.yaml file:

nullable-getter: false

After disabling nullable getters, you will be able to access the localization messages as shown below.

AppLocalizations.of(context).yourMessage

Get the selected locale

To get the selected locale of the widget tree, use the snippet shown below.

Locale selectedLocale = Localizations.localeOf(context);

Override the locale

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 an 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);
          },
        ),
      ),
    ],
  );
}

Change app language programmatically

We've observed how Flutter can identify the device's language settings, which is the user's preferred language, and tailor the app accordingly. We've also explored methods for overriding the language in specific areas of the app. What about incorporating a manual language switcher within the app, though? Fortunately, this isn't a complex task. It simply requires setting the selected language as a locale property to the MaterialApp widget. For more details, please refer to the provided example.


Part 2: Managing your Flutter localizations

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 dozens of languages. Here is how to automate your text string localization:

Step 1: Create a project in Localizely

Once you sign up to Localizely, just go to the My projects page and tap the “+” 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.


Flutter localization create project


Each project receives a unique ID, which can be found on the My projects page and is required when using the API.

Step 2: Upload your files

Import your main ARB file into Localizely by navigating to the Upload page. Select the file and click confirm. Alternatively, you can begin by adding string keys directly in Localizely.


Flutter localization arb file upload


Step 3: Invite team members

Localization work is a team effort. Click the icon in the side menu to switch to the Contributors section and add teammates. Any user can be granted admin privileges, i.e., the same rights on the project as you have. For non-admin contributors, you can specify access on a per-language basis. Assign some languages as reference (read-only) and others as contributable (read and update). Only admins have access to string key changes, importing, exporting, settings, and so on.


Flutter localization invite team member


Step 4: Translate

Localizely has an editor on the Translations page that resembles Excel tables commonly used for translation management. Feel free to explore the Sample project or take a look at the Getting started guide to grasp the basic concepts and options.


Flutter localization translate


Step 5: Download the files

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:

  • Option 1: Download manually

    Click the Download icon in your project's side menu and select Flutter ARB as the exporting format. Click the Download button to obtain the file. Then move the downloaded .arb file into your project, replacing the existing localization; that’s all.


    Flutter localization download arb file


  • Option 2: Download via API

    Depending on your project deployment setup, you may want to use the Localizely API. It gives you a simple way to automatically generate and download localization files.

Like this article? Share it!


Zoran Luledzija
Zoran Luledzija

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.

Enjoying the read?

Subscribe to the Localizely blog newsletter for quality product content in your inbox.

Related

How to translate ARB files efficiently
March 01, 2024
In “Localization
Localization Statistics 2023
December 22, 2023
In “Localization
Copyrights 2024 © Localizely