32 Adapting Internationalization Beyond Multiple Languages, What Else Should We Pay Attention To

32 Adapting Internationalization Beyond Multiple Languages, What Else Should We Pay Attention To #

Hello, I’m Chen Hang. Today, let’s talk about internationalization of Flutter applications.

With the help of the App Store and Google Play, we can publish our applications to any app store around the world. The (potential) users of the application may come from different countries and speak different languages. If we want to provide a unified and standardized experience for users worldwide, then the first thing we need to do is to make the app support multiple languages. This process is generally referred to as “internationalization”.

When it comes to internationalization, you might think it means translating all visible text within the app. However, this view is not accurate enough. A more accurate description of the responsibilities of internationalization is the “adaptation and transformation process involving language and regional differences”.

For example, if we want to display an amount of money, the same value would be displayed as ¥100 in China and $100 in the United States. Another example is the app’s onboarding screen. In China, we might use the Great Wall as the background, while in the United States, we might choose the Golden Gate Bridge as the background.

Therefore, the specific process of internationalizing an app not only involves translating texts, but also designing resources such as currency units and background images to be adaptable variables based on different regions. This also means that when designing the app architecture, we need to separate the parts that differ based on language and region.

In fact, this is also the overall approach to internationalization in Flutter, which is configuration extraction of language differences + internationalization code generation. In the process of configuration extraction, there is actually no substantial difference in handling texts, currency units, and background image resources. So in today’s sharing session, I will mainly focus on multi-language texts and explain how to achieve the independence of language and regional differences in Flutter. I believe that after learning this part of the knowledge, you will be able to easily handle internationalization for other types of language differences as well.

Flutter i18n #

In Flutter, the configuration of language and region for internationalization is part of the application’s code. To implement text internationalization in Flutter, we need to follow these steps:

  • First, implement a LocalizationsDelegate and declare all the text that needs translation as its properties.
  • Then, manually translate and adapt the text for each supported language and region.
  • Finally, during the initialization of the MaterialApp, set the LocalizationsDelegate as the translation callback for the application.

If we want to add or remove a language or text during development, we need to modify the program code.

At this point, you may notice that using the official internationalization approach provided by Flutter requires a lot of work and is prone to errors. Therefore, to start the internationalization journey for a Flutter application, it may be better to set aside the official solution and directly start learning from the Flutter i18n plugin in Android Studio. This plugin provides encapsulation for different language and region configurations and can automatically generate Dart code from translation files.

To install the Flutter i18n plugin, open the Preferences option in Android Studio, switch to the Plugins tab on the left, search for the plugin, and click on install. After installation, restart Android Studio to use the plugin.

Figure 1: Installing the Flutter i18n plugin

Flutter i18n depends on the flutter_localizations plugin package, so we also need to declare the dependency on it in the pubspec.yaml file, otherwise, the program will throw an error:

dependencies:
  flutter_localizations:
    sdk: flutter

You will notice that a values/strings_en.arb file is added under the res folder.

An .arb file is a JSON formatted configuration file that stores key-value pairs for text identifiers and translations. So, when we modify the .arb file under res/values, the i18n plugin will automatically generate corresponding code for us.

The strings_en file is the default English resource configuration. To support Chinese, we also need to add a strings_zh.arb file under the values directory:

Figure 2: .arb file format

Try modifying the strings_zh.arb file and you will see that the Flutter i18n plugin automatically generates the generated/i18n.dart file. This class not only provides static translations for resource identifiers, but also generates an inline function for dynamic text using the message_tip identifier:

Figure 3: Automatically generated code by the Flutter i18n plugin

Let’s continue to complete the strings_en.arb file, providing English translations. It is important to note that, as i18n.dart is generated by the plugin and will be automatically updated with any changes to the .arb file, manually editing this file should be avoided.

Next, let’s demonstrate how to implement internationalization in Flutter using the Flutter’s official template, the counter demo.

In the code below, we set two important parameters for internationalization during the initialization of the MaterialApp: localizationsDelegates and supportedLocales. The former is the translation callback for the application, and the latter is the supported language and region attributes.

S.delegate is a class generated by the Flutter i18n plugin, which includes the supported language and region attributes, along with the corresponding translations. In theory, using this class should be sufficient for complete internationalization of the application. However, why did we include the GlobalMaterialLocalizations.delegate and GlobalWidgetsLocalizations.delegate callbacks as well when configuring the translation callback for the application?

This is because Flutter’s provided widgets already support internationalization, so there is no need to translate them again. These two classes are the official translation callbacks provided by Flutter. In fact, the flutter_localizations plugin package we declared in the pubspec.yaml file earlier is a translation suite provided by Flutter, and these two classes are prominent members of that suite.

After completing the internationalization configuration for the application, we can retrieve translated text from the .arb files using S.of(context) in the application code.

However, it is important to note that the code for retrieving translated text will only work if it has access to the translation context, which means it will only work for the child widgets of MaterialApp. Therefore, in this configuration, we cannot internationalize the title attribute of MaterialApp directly. However, fortunately, MaterialApp provides a callback method, onGenerateTitle, to provide the translation context, so we can use it to internationalize the title text:

// Application entry point
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: const [
        // S.delegate, // Uncomment this line if only using the Flutter i18n plugin
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: const [
        Locale('en', 'US'),
        Locale('zh', 'CN'),
      ],
      onGenerateTitle: (BuildContext context) => S.of(context).appTitle,
      home: MyHomePage(),
    );
  }
}

In the code above, we have set two important parameters for internationalization, localizationsDelegates and supportedLocales, during the initialization of MaterialApp. The S.delegate line, which is generated by the Flutter i18n plugin, should be uncommented if only using the plugin’s internationalization support. The GlobalMaterialLocalizations.delegate and GlobalWidgetsLocalizations.delegate callbacks are added because Flutter’s provided widget classes support internationalization out of the box. The supportedLocales list specifies the supported language and region combinations. Finally, we use the onGenerateTitle callback to provide the internationalized title text using the S.of(context).appTitle code.

After completing these steps, we can use the S.of(context) code to retrieve translated text defined in the .arb files.

Please note that the code for retrieving translated text will only work for the child widgets of MaterialApp, where the translation context is accessible.

S.delegate, // Translation callback for the application
GlobalMaterialLocalizations.delegate, // Translation callback for Material components
GlobalWidgetsLocalizations.delegate, // Translation callback for general widgets
],
supportedLocales: S.delegate.supportedLocales, // Supported locales
// Internationalization callback for title
onGenerateTitle: (context) {
  return S.of(context).app_title;
},
home: MyHomePage(),
);
}
}

Translating the main interface copy of the application is relatively simple. You can directly retrieve the translated copy declared in the arb file by using the S.of(context) method:

Widget build(BuildContext context) {
  return Scaffold(
    // Get the translated copy of the app bar title
    appBar: AppBar(
      title: Text(S.of(context).main_title),
    ),
    body: Center(
      // Pass in the _counter parameter to get the counter dynamic copy
      child: Text(
        S.of(context).message_tip(_counter.toString())
      ),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: _incrementCounter, // Callback for click
      tooltip: 'Increment',
      child: Icon(Icons.add),
    ),
  );
}

On an Android device, when you switch between English and Chinese systems, you can see that the counter application correctly handles multilingualism.

Figure 4 Counter example (Android English system)

Figure 5 Counter example (Android Chinese system)

Since iOS applications have their own language environment management mechanism, which defaults to English, in order to properly support internationalization in iOS applications, we need to make additional configurations in the native iOS project. Open the native iOS project and switch to the project panel. In the Localization configuration, we can see that the iOS project already supports English by default, so we need to click the “+” button to add Chinese:

Figure 6 Chinese configuration in iOS project

After completing the iOS project configuration, we return to the Flutter project and select to run the program on the iOS device. You can see that the iOS version of the counter application also correctly supports internationalization.

Figure 7 Counter example (iOS English system)

Figure 8 Counter example (iOS Chinese system)

Native Project Configuration #

The internationalization solutions mentioned above are actually implemented within the Flutter application. Before running the Flutter framework, we cannot access these localized texts.

Flutter requires a native environment to run, but for some texts, such as the application name, we need to provide multiple language versions for it before the Flutter framework runs (e.g. the English version is “computer” and the Chinese version is “计数器”). This requires configuring the corresponding internationalization settings in the native project.

Let’s start by configuring the application name in the Android project.

In the Android project, the application name is declared in the android:label attribute of the application tag in the AndroidManifest.xml file. We need to modify it to a reference to a string resource so that it can automatically choose the appropriate localized text based on the language and region:

<manifest ... >
    ... 
    <!-- Set application name -->
	<application
        ...
	    android:label="@string/title"
        ...
    >
	</application>
</manifest>

Next, we need to create a strings.xml file for each supported language in the android/app/src/main/res folder. Since the default file is in English, we only need to create one file for Chinese. The file structure for string resources is shown in the following diagram:

Diagram 9: strings.xml file structure

The contents of strings.xml in the values and values-zh folders are as follows:

<!-- English (default) string resources -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="title">Computer</string>
</resources>


<!-- Chinese string resources -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="title">计数器</string>
</resources>

After completing the project configuration for the Android application title, we return to the Flutter project and choose to run the program on an Android phone. We can see that the application title of “计数器” (counter in Chinese) now correctly supports internationalization.

Next, let’s see how to configure the application title in the iOS project.

Similar to the Android project, the application title in the iOS project is declared in the Bundle name property in the Info.plist file. We also need to modify it to a reference to a string resource so that it can automatically choose the localized text based on the language and region:

Diagram 10: iOS project application title configuration

Since the application title is not configurable by default in the project, we need to create resource files corresponding to the string references. Right-click on the Runner folder, then choose New File to add a string resource file named InfoPlist.strings. In the rightmost file inspector in the project panel, under the Localization option, add English and Chinese as two languages. The content of InfoPlist.strings for the English and Chinese versions is as follows:

// English version
"CFBundleName" = "Computer";

// Chinese version
"CFBundleName" = "计数器";

Thus, we have also completed the project configuration for the iOS application title. We return to the Flutter project and choose to run the program on an iOS device, and we will find that the application title of “计数器” (counter in Chinese) also supports internationalization.

Summary #

Alright, that’s all for today’s presentation. Let’s summarize the key points.

In today’s presentation, I introduced the solution for internationalizing Flutter applications. It involves implementing a LocalizationsDelegate in the code, where all the texts that need to be translated are declared as its properties. Then, manual translation adaptation is performed one by one, and finally, this delegate class is set as the translation callback for the application.

To simplify the process of manual translation to code conversion, we usually use multiple ARB files to store the mapping between texts and different language regions. The Flutter i18n plugin is used to automatically convert the code.

The core of internationalization is the extraction of language differences configuration. For internationalization adaptation in native Android and iOS systems, we just need to provide different folder directories for the resources that need to be internationalized (such as string texts, images, layouts, etc.). When accessing internationalized resources in the application code, they will be automatically adapted according to the language region.

However, the internationalization capability of Flutter is relatively primitive. Internationalized resources for different languages and regions are neither stored in separate XML or JSON files, nor stored in different language and region folders. Fortunately, we have the help of the Flutter i18n plugin. Otherwise, providing internationalization support for an application would be an extremely tedious task.

I have packaged the knowledge points covered in today’s presentation on GitHub. You can download it and run it multiple times to deepen your understanding and memory.

Reflection Question #

Finally, I will leave you with a reflection question.

How can you achieve internationalization of image resources in Flutter?

Feel free to leave your thoughts in the comments, and I will be waiting for you in the next article! Thank you for listening, and please feel free to share this post with more friends to read together.