Flutter (86): Make App support multiple languages

Time: Column:Mobile & Frontend views:201

1 Introduction

If our application needs to support multiple languages, we need to "internationalize" it. This means that during development, we need to set "localized" values for each language environment that the application supports, such as text and layout. The Flutter SDK provides several components and classes to help us achieve internationalization. Below, we will introduce the steps for implementing internationalization in Flutter.

Next, we will illustrate how to support internationalization using an application that has MaterialApp as its entry point.

Most applications use MaterialApp as the entry point, but applications built using the lower-level WidgetsApp class can also use the same classes and logic for internationalization. In fact, MaterialApp is a wrapper around WidgetsApp.

Note that "localized values and resources" refer to the different resources we prepare for different languages, generally consisting of text (strings). There may also be other resources that vary by language region, such as images of flags for the countries where the app is available; different locales will require different flag images.

2 Supporting Internationalization

By default, the components in the Flutter SDK only provide localization resources for American English (primarily text). To add support for other languages, the application must add a package dependency called flutter_localizations, and some configuration is also required in MaterialApp. To use the flutter_localizations package, you first need to add the dependency to the pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

Next, download the flutter_localizations library and specify the localizationsDelegates and supportedLocales in MaterialApp:

import 'package:flutter_localizations/flutter_localizations.dart';

MaterialApp(
 localizationsDelegates: [
   // Localization delegate
   GlobalMaterialLocalizations.delegate,
   GlobalWidgetsLocalizations.delegate,
 ],
 supportedLocales: [
    const Locale('en', 'US'), // American English
    const Locale('zh', 'CN'), // Simplified Chinese
    // Other locales
  ],
  // ...
)

Unlike applications that use MaterialApp as the entry point, applications based on WidgetsApp do not require GlobalMaterialLocalizations.delegate for internationalization.

The elements in the localizationsDelegates list are factory classes that generate the set of localized values. GlobalMaterialLocalizations.delegate provides localized strings and other values for the Material component library, enabling it to support multiple languages. GlobalWidgetsLocalizations.delegate defines the default text direction of components, either left-to-right or right-to-left, because some languages, such as Arabic, are read from right to left.

SupportedLocales also accepts an array of Locale, indicating the list of languages supported by our application. In this example, our application supports only American English and Simplified Chinese.

3 Getting the Current Locale

The Locale class is used to identify the user's language environment, including both language and country indicators, such as:

const Locale('zh', 'CN') // Simplified Chinese

We can always get the application's current locale using the following method:

Locale myLocale = Localizations.localeOf(context);

The Localizations component is typically positioned at the top of the widget tree, above other business components. Its purpose is to define the locale and set the localized resources that depend on the subtree. If the system's language environment changes, the corresponding localized resources will be used.

4 Listening for System Language Changes

When we change the system language settings, the Localizations component in the app will rebuild, and the Locale obtained from Localizations.localeOf(context) will be updated, resulting in the interface being rebuilt to reflect the language switch. However, this process is implicit, and we do not actively listen for system language changes. Sometimes, we may need to perform certain actions when the system language changes, such as setting a default language when the system switches to a language unsupported by our app. In such cases, we need to listen for locale change events.

We can listen for locale change events using the localeResolutionCallback or localeListResolutionCallback callbacks. Let’s first look at the signature of the localeResolutionCallback:

Locale Function(Locale locale, Iterable<Locale> supportedLocales)

The locale parameter represents the current system language setting. When the application starts or when the user dynamically changes the system language setting, this locale reflects the current system locale. If the developer manually specifies the app’s locale, this locale parameter will represent the specified locale, and the system locale will be ignored, for example:

MaterialApp(
 ...
 locale: const Locale('en', 'US'), // Manually specify locale
 ...
)

In the above example, the application's locale is manually set to American English. After specifying this, even if the device's current language is Simplified Chinese, the locale in the app remains American English. If locale is null, it means Flutter was unable to retrieve the device's locale information, so we must always check for null before using the locale.

The supportedLocales parameter represents the list of locales supported by the current application, which is registered by the developer in the MaterialApp through the supportedLocales property.

The return value is a Locale, which is the final locale that the Flutter app will use. Typically, a default locale is returned when the language region is unsupported.

The only difference between localeListResolutionCallback and localeResolutionCallback is the type of the first parameter; the former accepts a list of locales, while the latter accepts a single locale.

Locale Function(List<Locale> locales, Iterable<Locale> supportedLocales)

In newer Android systems, users can set a list of languages. Consequently, applications that support multiple languages will receive this list. The usual handling method is to sequentially try to load the corresponding locale according to the list's order; if a language loads successfully, it will stop. Figure  shows a screenshot of setting the language list in the Android system:

Flutter (86): Make App support multiple languages

Setting Language List

In Flutter, it is preferable to use localeListResolutionCallback. You need not worry about differences in Android versions; Flutter will automatically handle situations in lower versions, where the locale list will contain only one item.

5 Localization Component

The Localizations component is used to load and retrieve localized values or resources for the application's current language. The application references these objects through Localizations.of(context, type). If the device's locale settings change, the Localizations component will automatically load the locale values for the new region and then rebuild any components that depend on them. This occurs because Localizations internally uses InheritedWidget, as discussed earlier: when a child component's build function references an InheritedWidget, it creates an implicit dependency on it. Therefore, when the InheritedWidget changes—specifically, when the locale setting of Localizations changes—all dependent child components will be rebuilt.

The localized values are loaded from the LocalizationsDelegates list of Localizations. Each delegate must define an asynchronous load() method to generate an object that encapsulates a series of localized values. Typically, these objects define a method for each localized value.

In large applications, different modules or packages may bundle their localized values together. This is why we use Localizations to manage the object table. To use an object produced by one of the LocalizationsDelegate's load methods, you can specify a BuildContext and the type of the object to find it. For example, the localized strings for the Material component library are defined by the MaterialLocalizations class, and instances of this class are provided by the LocalizationDelegate from MaterialApp. They can be accessed as follows:

Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);

This specific Localizations.of() expression is frequently used, so the MaterialLocalizations class provides a convenient method:

static MaterialLocalizations of(BuildContext context) {
  return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
}

// Conveniently call the method
tooltip: MaterialLocalizations.of(context).backButtonTooltip,

6 Using Pre-packaged LocalizationsDelegates

To keep it as small and simple as possible, the Flutter package only provides implementations of the MaterialLocalizations and WidgetsLocalizations interfaces for American English values. These implementation classes are called DefaultMaterialLocalizations and DefaultWidgetsLocalizations, respectively. The flutter_localizations package includes multilingual implementations of the localization interfaces for GlobalMaterialLocalizations and GlobalWidgetsLocalizations. Internationalized applications must specify the localization delegate classes for these as described at the beginning of this section.

The aforementioned GlobalMaterialLocalizations and GlobalWidgetsLocalizations are only the localization implementations for the Material component library. If we want our own layouts to support multiple languages, we will need to implement our own Localizations, which we will discuss in the next section.