Flutter (88): Using the Intl package

Time: Column:Mobile & Frontend views:282

1 Adding Dependencies

By using the Intl package, we can not only easily implement internationalization but also separate string texts into individual files, facilitating collaboration between developers and translators. To use the Intl package, we need to add two dependencies:

dependencies:
  # ...other dependencies
  intl: ^0.17.0 dev_dependencies:
  # ...other dev dependencies
  intl_generator: 0.2.1

The intl_generator package primarily contains tools that extract strings to be internationalized from the code into separate arb files during the development phase, and generates corresponding Dart code from the arb files. The intl package is mainly used to reference and load the Dart code generated by intl_generator. Below, we will explain how to use it step by step.

2 Step One: Create Necessary Directories

First, create an l10n-arb directory in the root of the project to store the arb files generated by the intl_generator command. A simple arb file content is as follows:

{
  "@@last_modified": "2018-12-10T15:46:20.897228",
  "@@locale": "zh_CH",
  "title": "Flutter应用",
  "@title": {
    "description": "Title for the Demo application",
    "type": "text",
    "placeholders": {}
  }}

From the "@@locale" field, we can see that this arb corresponds to the translation in Simplified Chinese, and the title field corresponds to the Chinese translation of our application title. The @title field provides descriptive information about the title.

Next, create a l10n directory under the lib directory, which will be used to store the Dart code files generated from the arb files.

3 Step Two: Implement the Localizations and Delegate Classes

Similar to the previous section's steps, we still need to implement the Localizations and Delegate classes. The difference is that we will now use some methods from the intl package (some of which are dynamically generated).

Now, create a file named localization_intl.dart in the lib/l10n directory with the following content:

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'messages_all.dart'; //1

class DemoLocalizations {
  static Future<DemoLocalizations> load(Locale locale) {
    final String name = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
    final String localeName = Intl.canonicalizedLocale(name);
    //2
    return initializeMessages(localeName).then((b) {
      Intl.defaultLocale = localeName;
      return DemoLocalizations();
    });
  }

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

  String get title {
    return Intl.message(
      'Flutter APP',
      name: 'title',
      desc: 'Title for the Demo application',
    );
  }
}

// Locale delegate class
class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations> {
  const DemoLocalizationsDelegate();

  // Check if a specific locale is supported
  @override
  bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode);

  // Flutter will call this method to load the corresponding Locale resource class
  @override
  Future<DemoLocalizations> load(Locale locale) {
    //3
    return DemoLocalizations.load(locale);
  }

  // Whether to call load to reload Locale resources when the Localizations Widget rebuilds
  @override
  bool shouldReload(DemoLocalizationsDelegate old) => false;
}

Note:

  • The messages_all.dart file (comment 1) is generated from the arb files using the intl_generator tool, so this file does not exist before running the generation command for the first time.

  • The initializeMessages() method (comment 2) is generated along with the messages_all.dart file.

  • At comment 3, unlike the previous section's example code, we directly call DemoLocalizations.load() here.

4 Step Three: Adding Internationalized Properties

Now we can add the properties or methods that need to be internationalized in the DemoLocalizations class, such as the title property in the example code above. At this point, we will utilize some methods provided by the Intl library that help us easily implement grammatical features of different languages, such as pluralization. For example, if we have an email list page, we may need to display the number of unread emails at the top, and the text we show might differ depending on the count of unread emails:

Unread Emails CountMessage
0There are no emails left
1There is 1 email left
n (n > 1)There are n emails left

We can implement this using Intl.plural(...):

remainingEmailsMessage(int howMany) => Intl.plural(howMany,
    zero: 'There are no emails left',
    one: 'There is $howMany email left',
    other: 'There are $howMany emails left',
    name: "remainingEmailsMessage",
    args: [howMany],
    desc: "How many emails remain after archiving.",
    examples: const {'howMany': 42, 'userName': 'Fred'});

As seen, the Intl.plural method allows us to output different messages based on the value of howMany.

The Intl package also has other methods, which readers can refer to in its documentation; this book will not elaborate further.

5 Step Four: Generating arb Files

Now we can use the tools in the intl_generator package to extract strings from the code into an arb file. Run the following command:

flutter pub run intl_generator:extract_to_arb --output-dir=l10n-arb lib/l10n/localization_intl.dart

After running this command, the properties and strings we identified using the Intl API will be extracted to the “l10n-arb/intl_messages.arb” file. Let’s take a look at its content:

{
  "@@last_modified": "2018-12-10T17:37:28.505088",
  "title": "Flutter APP",
  "@title": {
    "description": "Title for the Demo application",
    "type": "text",
    "placeholders": {}
  },
  "remainingEmailsMessage": "{howMany,plural, =0{There are no emails left}=1{There is {howMany} email left}other{There are {howMany} emails left}}",
  "@remainingEmailsMessage": {
    "description": "How many emails remain after archiving.",
    "type": "text",
    "placeholders": {
      "howMany": {
        "example": 42
      }
    }
  }}

This is the default locale resource file. If we want to support Simplified Chinese now, we simply need to create an "intl_zh_CN.arb" file in the same directory and copy the contents of "intl_messages.arb" into "intl_zh_CN.arb." Next, we will translate the English text into Chinese. The translated content of "intl_zh_CN.arb" is as follows:

{
  "@@last_modified": "2018-12-10T15:46:20.897228",
  "@@locale": "zh_CN",
  "title": "Flutter应用",
  "@title": {
    "description": "Title for the Demo application",
    "type": "text",
    "placeholders": {}
  },
  "remainingEmailsMessage": "{howMany,plural, =0{没有未读邮件}=1{有{howMany}封未读邮件}other{有{howMany}封未读邮件}}",
  "@remainingEmailsMessage": {
    "description": "How many emails remain after archiving.",
    "type": "text",
    "placeholders": {
      "howMany": {
        "example": 42
      }
    }
  }}

We must translate the title and remainingEmailsMessage fields; the description provides explanations for these fields, usually for the translators' reference and not used in the code.

Two points to note:

  1. If a specific arb file is missing a property, the application will load the corresponding property from the default arb file (intl_messages.arb). This is the fallback strategy of Intl.

  2. Every time we run the extraction command, intl_messages.arb will be regenerated based on the code, while other arb files will not be overwritten. Therefore, when adding new fields or methods, the other arb files are incrementally updated, so there’s no need to worry about overwriting.

The arb file format is standard, and you can find more details on its specifications. Typically, arb files are handed to translators, and after they complete the translations, we follow the steps below to generate the final Dart code based on the arb files.

6 Step Five: Generating Dart Code

The final step is to generate Dart files from the arb files:

flutter pub run intl_generator:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/l10n/localization_intl.dart l10n-arb/intl_*.arb

When run for the first time, this command will generate multiple files in the "lib/l10n" directory corresponding to various locales, and this code will be the Dart code we ultimately use.

7 Summary

At this point, we have introduced the process of internationalizing the app using the Intl package. We can see that the first two steps are only necessary during the initial setup, while the main development work occurs in the third step. Since the last two steps need to be executed each time after completing the third step, we can place them in a shell script. After completing the third step or translating the arb files, we can simply run this script. We create a script named intl.sh in the root directory with the following content:

flutter pub run intl_generator:extract_to_arb --output-dir=l10n-arb lib/l10n/localization_intl.dart
flutter pub run intl_generator:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/l10n/localization_intl.dart l10n-arb/intl_*.arb

Then, grant it execution permissions:

chmod +x intl.sh

Execute intl.sh:

./intl.sh