How to internationalize your Flutter app with ARB files today? — full-blown tutorial
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.arb
will 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 intype
. - You need to use
=0
instead ofzero
,=1
instead ofone
, and=2
instead oftwo
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 withtype
,example
,format
,description
and/oroptionalParameters
.
Available placeholder type
s: 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 tolib/l10n
.template-arb-file
(or--templ
… you get the idea) — a filename of your template ARB file (explained above), insidearb-dir
. Defaults toapp_en.arb
.synthetic-package
— a boolean whether to use a synthetic package. Iftrue
, your output files will be created in the.dart_tool/flutter_gen/gen_l10n
directory which you most likely excluded from Git. Iffalse
, theoutput-dir
is used. Defaults totrue
.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 toAppLocalizations
.untranslated-message-file
— a relative path for a JSON file with a map of locale-message names of untranslated messages. Defaults tonull
, 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 withheader-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 withheader
.preferred-supported-locales
— a list of locales in order they will be passed inAppLocalizations.supportedLocales
. ARB files with locales not specified in this list will still be present insupportedLocales
, but in the default directory traverse (alphabetical) rather than the preferred one.
If we supporteden
andaz
languages, a user withpl
as one’s default locale would have the app in Azerbaijan by default. Specifyingen
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 tofalse
.nullable-getter
— a boolean whether the staticof
method in your localization delegate should return a nullable string or do a null-check inside. Defaults totrue
.format
— a boolean whether to format the outputted files. Defaults totrue
.
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 anoutput-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:
- Open your project’s
ios/Runner.xcworkspace
Xcode file. - In the Project Navigator, open the
Info.plist
file under theRunner
project’sRunner
folder. - Select the Information Property List item. Then select Add Item from the Editor menu, and select Localizations from the pop-up menu.
- 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. - 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:
- Internationalizing Flutter apps on Flutter.dev
- Internationalization User Guide by Flutter Team
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!