How to internationalize your Flutter app with ARB files today? — full-blown tutorial

Albert Wolszon
7 min readOct 2, 2022

--

This article explains what is today’s (as of October 2022, Flutter 3.3) Flutter-native way to manage your ARB internationalization files, without the help of any external tools like intl_utils or similar.

This article is no longer 100% true with Flutter 3.7 and above, as there were some changes in ARB parsing, new options added and so on. This article may be updated in the future.

This also serves as a kind of reference knowledge that I gained during the development of Arbify (the public version as well as a private v2) and the poe2arb tool.

ARB files

Your ARB files divide as follows:

  • Template ARB file — usually English or the main language of your application. This is the file that also contains metadata, such as the description of the messages and placeholders with their corresponding type, format, example, and description. You mark one of your files as a template in configuration (see l10n.yaml below).
  • Other ARB files — all the languages you support, besides the one used in the template ARB file. This file shouldn’t have any metadata, as it’s simply ignored.

ARB files syntax

Let’s start with explaining the syntax of an ARB file.

// app_en.arb
{
"@@locale": "en",
"app_title": "Fast Shopping",
"@app_title": {
"description": "Shown on top of the screen."
},
"list_item_no_name": "No name",
"@list_item_no_name": {
"description": "When the item has no/empty name."
},
"list_item_done_ago": "done {when}",
"@list_item_done_ago": {
"description": "Small caps date when item was marked as done.",
"placeholders": {
"when": {
"type": "String",
"example": "a minute ago"
}
}
},
}

This is my template ARB file.

Locale

Flutter checks the ARB file name for a string with one of Flutter recognized ISO 639 language identifiers. It must be prefixed with an underscore, so names like app_en.arb, my_messages_fr_ca.arbwill be recognized, while simply en.arb won’t be. The language and the following rest of the filename are then parsed using LocaleInfo.fromString.

If the ARB file also has @@locale key (which is optional), Flutter checks if it matches the locale pulled from the filename. If it does not, an exception is thrown.

Last modified

Some tools output the @@last_modified key that contains an ISO 8601 date of the last modification of the file and/or translations.

Messages

Messages are the key parts of the ARB files. Each message name corresponds to a message value with the string internationalized to a given locale.

The message name could be any string that is a valid Dart field name.

The message value could be any string. It can contain variables, called placeholders. It can also contain a subset of the ICU MessageFormat syntax (with some caveats below) for plural and select (although no gender support).

For the correct parsing of the variables, both plain ones and those used in the ICU strings, you must define the variables in the message’s metadata placeholders field.

"delete_shopping_list_dialog_body": "Do you really want to delete {list_name} shopping list? This operation cannot be undone.",
"@delete_shopping_list_dialog_body": {
"placeholders": {
"list_name": {
"type": "String",
"example": "Groceries"
}
}
},
"remove_all_done_dialog_body": {count, plural, =1 {Are you sure you want to remove 1 item? This operation cannot be undone.} other {Are you sure you want to remove all {count} items? This operation cannot be undone.}}",
"@remove_all_done_dialog_body": {
"placeholders": {
"count": {
"type": "int",
"format": "compactLong"
}
}
},

You can also take a small shortcut here and simply use "placeholder_name": {} to make it an Object type.

Some remarks:

  • The plural message format’s count placeholder is always of type int, no matter what you specify in type.
  • You need to use =0 instead of zero, =1 instead of one, and =2 instead of two as plural categories in the plural message format.
  • Flutter doesn’t support offset in the plural message format.

Message metadata

Message metadata resides in a JSON object with the key being the message’s name, prefixed with a single @.

This object may contain:

  • description with a simple explanation of what a given message is. It’s added to the Dartdocs of generated getters/methods for the l10n classes.
  • placeholders object that contains a map of placeholder name to an object with type, example, format, description and/or optionalParameters.

Available placeholder types: double, int, num, DateTime, Object (default), and any other type.

double, int, and num placeholder types must come with the format parameter that is one of the following: compact, compactCurrency, compactSimpleCurrency, compactLong, currency, decimalPattern, decimalPercentPattern, percentPattern, scientificPattern, or simpleCurrency.

If some of those number formats (like compactCurrency) have any additional parameters, you may specify them in the optionalParameters map.

DateTime placeholder types must also come with the format parameter with a value of one of the available DateFormat constructors, from here: https://github.com/flutter/flutter/blob/ce318b7b539e228b806f81b3fa7b33793c2a2685/packages/flutter_tools/lib/src/localizations/gen_l10n_types.dart#L33-L75.

Sadly, both example and description placeholder attributes aren’t outputted to the result Dart files. But they still can be used by external translation management tools, or by developers when reading the ARB files directly.

l10n.yaml configuration file and CLI arguments

Below is a list of the configuration options. Those can be specified in your l10n.yaml file (residing next to pubspec.yaml), or as flutter gen-l10n command arguments.

If an l10.yaml file exists, the CLI arguments are ignored.

  • arb-dir (or --arb-dir CLI argument) — a relative path to the directory containing all the ARB files. Defaults to lib/l10n.
  • template-arb-file (or --templ… you get the idea) — a filename of your template ARB file (explained above), inside arb-dir. Defaults to app_en.arb.
  • synthetic-package — a boolean whether to use a synthetic package. If true, your output files will be created in the .dart_tool/flutter_gen/gen_l10n directory which you most likely excluded from Git. If false, the output-dir is used. Defaults to true.
  • output-dir — a relative path to the directory where the output Dart files will be generated if the synthetic package is not used.
  • output-localization-file — a filename of your result Dart file. Additional files for specific locales will have the locale appended after the an underscore.
  • output-class — a name of the class for your localization delegates and strings. Defaults to AppLocalizations.
  • untranslated-message-file — a relative path for a JSON file with a map of locale-message names of untranslated messages. Defaults to null, which does not generate such file.
  • header — a string that is prepended to the very top of the output file, above the first import. Exclusive with header-file.
  • header-file — a relative path to the file which contains a string that is prepended to the very top of the output file, above the first import. Exclusive with header.
  • preferred-supported-locales — a list of locales in order they will be passed in AppLocalizations.supportedLocales. ARB files with locales not specified in this list will still be present in supportedLocales, but in the default directory traverse (alphabetical) rather than the preferred one.
    If we supported en and az languages, a user with pl as one’s default locale would have the app in Azerbaijan by default. Specifying en first in this option would put English before Azerbaijan in the locale lookup.
  • use-deferred-loading — a boolean whether to use deferred loading for different locale localization classes, used on Flutter Web only.
  • required-resource-attributes — a boolean whether to throw when there is no metadata for a message in the template ARB file. Defaults to false.
  • nullable-getter — a boolean whether the static of method in your localization delegate should return a nullable string or do a null-check inside. Defaults to true.
  • format — a boolean whether to format the outputted files. Defaults to true.

Here’s some example l10n.yaml file:

# l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: S
preferred-supported-locales: [ en ]

pubspec.yaml

Flutter localizations has two dependencies:

# pubspec.yaml
dependencies:
flutter_localizations:
sdk: flutter
intl: ^0.17.0 # or whatever version is latest/you need

If you use synthetic package (synthetic-package flag explained above), you must have generate: true in the flutter section of your pubspec.yaml:

# pubspec.yaml
flutter:
generate: true

Enabled synthetic package generation may cause conflicts with the build_runner though. If you stumble upon errors such as this, stop using synthetic package and provide an output-dir for your result Dart files, just as I explained here.

iOS configuration

iOS applications define key application metadata, including supported locales, in an Info.plist file that is built into the application bundle. To configure the locales supported by your app, use the following instructions:

  1. Open your project’s ios/Runner.xcworkspace Xcode file.
  2. In the Project Navigator, open the Info.plist file under the Runner project’s Runner folder.
  3. Select the Information Property List item. Then select Add Item from the Editor menu, and select Localizations from the pop-up menu.
  4. Select and expand the newly-created Localizations item. For each locale your application supports, add a new item and select the locale you wish to add from the pop-up menu in the Value field. This list should be consistent with the languages listed in the supportedLocales parameter.
  5. Once all supported locales have been added, save the file.

This section is from the official docs.

Your MaterialApp

…or CupertinoApp or WidgetsApp.

Import the generated Dart file from the generated synthetic package (package:flutter_gen/gen_l10n/<output-localization-file>.dart) or your path (<output-dir>/<output-localization-file>.dart).

Pass AppLocalizations.supportedLocales (or whatever output-class name you’ve chosen) to your app’s supportedLocales.

Pass AppLocalizations.delegate to your localizationDelegates class. Instead of this, you can too use the AppLocalizations.localizationDelegates for convenience, which includes the widgets, Material, and Cupertino delegates as well, out-of-the-box.

Widget build(BuildContext context) {
return MaterialApp(
// (...)
// Localization stuff
onGenerateTitle: (context) => S.of(context).app_title,
supportedLocales: S.supportedLocales,
localizationsDelegates: S.localizationsDelegates,
// (...)
);
}

Generate command

That’s all you need! Now you can generate all the Dart files based on your ARB files using:

flutter gen-l10n

It’s also done on flutter build and on hot reloads.

And that’s it! Now you can import your app delegate and do AppLocalizations.of(context).someMessage to display your precious internationalized strings!

Here’s some further reading if you want to deep dive into the topic:

If you found this article helpful, I’d be grateful if you would 👏 clap or share it on Twitter or on your company’s Slack. Thanks!

--

--