Application Resource Bundle (.arb) for Dart & Flutter
Application Resource Bundle (or .ARB
) is a localization file format developed by Google. It is a subset of the JSON
-format and has functionality for pluralization, select branches (e.g. for genders), date, number, and currency formatting.
It forms the basis of the intl
package, which is endorsed by the Flutter team and recommended for internationalizing Flutter apps, but can also be adopted for pure-Dart projects.
Lyrebird makes it trivial to work with Application Resource Bundles by providing an easy-to-use visual editor.
Table of Contents
File structure
Each Application Resource Bundle file specifies all translations for a single language. As such, an application may have exactly one file per language that it supports.
The following code-snipped shows an example .ARB
file.
{
"@@locale": "en",
"appName": "Demo app",
"welcomeMessage": "Welcome {name}",
"@welcomeMessage": {
"description": "Says hello to the user",
"placeholders": {
"name": {}
}
},
}
In essence, it is a key-value structure, where the key is an identifier shared across languages, and the value is the translation for the language described by the file. Values are encoded using the ICU syntax, a standard supported by many localization tools.
Metadata keys start with an @
-symbol and describe their respective non-@
counterpart with a JSON
-object which will be further explained in subsequent sections. Here, the @welcomeMessage
key-value pair adds metadata to the welcomeMessage
translation.
Keys starting with @@
describe the file as a whole. For example, the value of the @@locale
entry suggests our example contains English translations. Note that this field is not always required, as the locale can also be inferred from the filename on disk. A file called lyrebird_de.arb
is assumed to have German translations.
The ICU Syntax
As previously mentioned, translation strings are encoded using the ICU syntax. This allows us to represent advanced sentence structures like pluralization or gendering based on an argument. In this section, we examine the ICU syntax in more detail.
Plain Text
The most basic (and perhaps most common) way to translate messages is through plain text. For many scenarios, the string displayed in an app does not need to change depending on the context. For example, the title of a page in a mobile application is often static.
{
...
"myPageTitle": "My Awesome Page",
...
}
Newlines
As is expected in JSON-formatted strings, we can use \n
to represent line breaks in our translation strings.
{
...
"myMessage": "This is the first line\nAnd this is the second line!",
...
}
Arguments
In some scenarios, we want to insert a string into our translation that is not known ahead of time. For example, a message that greets the user should contain the username in the correct spot in every language.
We can represent arguments, that is, placeholder slots that can be programmatically filled with content later on, using curly braces.
{
...
"userGreeting": "Welcome to Lyrebird, {username}!",
...
}
The identifier inside the curly braces specifies the name of the argument and allows us to reference it later on from Dart code. This identifier must be defined as a placeholder.
NOTE
We can use an unlimited amount of arguments in our translation string, and even reuse them as many times as is desired.
Select
The select
syntax can be used when we want to display different translation based on a nominal argument value. It is best explained with an example.
{
"flavor": "Their favorite flavor is {favoriteFlavor, select, chocolate{sweet chocolate} strawberry{fruity strawberry} other{something else}}",
}
Here, favoriteFlavor
is an argument passed to us from Dart code that acts as the condition. The select
expression displays the case where the key (e.g. chocolate
, strawberry
, other
) matches favoriteFlavor
, or other
if it does not match anything.
The argument must be specified as a placeholder.
DANGER
The other
case is required. If you do not make use of it, you can leave it empty (e.g. other{}
).
Genders
The select
syntax can also be used to translate sentences with multiple genders.
{
"iceCream": "{gender, select, male{He likes ice cream} female{She likes ice cream} other{They like ice cream}}",
}
DANGER
As with any other select
expression, the other
case is required.
Plural
The plural
syntax can be used when the grammar of a translation depends on an argument's cardinality. It is best explained with an example.
{
"newNotifications": "{notificationCount, plural, zero{No new notifications.} one{One new notification.} other{{notificationCount} new notifications!}}",
}
Here, notificationCount
is an integer argument passed to us from Dart code. Depending on if this integer is zero, one, or something else entirely, we display a different translation.
The argument must be specified as a placeholder.
DANGER
Just like with select
, the other
case is required.
NOTE
We can also nest ICU expressions in the plural cases, as demonstrated by notificationCount
as a regular argument in the other
case.
Plural Arguments
We can use #
as a shorthand to reference the cardinality argument of a plural
expression within the plural case expressions.
{
"myPlural": "{count, plural, zero{{count} notifications.} one{{count} notification.} other{{count} notifications!}}",
"myPlural": "{count, plural, zero{# notifications.} one{# notification.} other{# notifications!}}",
}
Here, {count}
is replaced with #
to simplify our expression.
WARNING
#
is treated as a regular text symbol outside of plural
expressions.
Translation Metadata
Metadata can optionally be added to any translation key, by adding an additional JSON
-key to the root object. This key should be identical to the translation key that is to be described, but be prefixed with an @
-symbol.
{
...
"myTranslationKey": "My translated string!",
"@myTranslationKey": {
// Metadata is specified here
}
...
}
Description
As part of a translation string's metadata, we can optionally write a description. This should convey meaning to a human translator as to what the context and intended use-case of the translation key is, so that the proper semantics carry over to other languages.
{
...
"userGreeting": "Welcome to Lyrebird, {username}.",
"@userGreeting": {
"description": "Greets the user on the landing page. This should be written politely and formally, since we treat our users like royalty.",
...
}
...
}
Placeholders
Coming soon™
Context
This field is currently unused by the intl
package but can nevertheless optionally be accessed and edited with Lyrebird to add additional context information to a translation key.
{
...
"usernameFieldErrorMessage": "This username is invalid.",
"@usernameFieldErrorMessage": {
...
"context": "auth:signup:form",
...
}
...
}
NOTE
You can use any formatting you like for the context string.
File Metadata
File metadata is specified using special key-value pairs (starting with @@
) in the root JSON-object of the file that are not treated as translation keys.
Locale
The locale descriptor tells the parser the language of all translation values in this file. To create a multilingual application, we require multiple ARB-files, each with their own locale descriptor.
{
"@@locale": "en",
...
"myMessage": "This is the English translation of myMessage!",
...
}
NOTE
The locale can also be inferred from the filename on disk. A file called lyrebird_de.arb
is assumed to have German translations.
Last Modified
Specifies last time this ARB file was modified in the ISO 8601 format.
{
...
"@@last_modified": "2023-01-04T13:12:36+00:00",
...
}
NOTE
This value is currently ignored by both Lyrebird as well as intl
.
Context
Similarly to the context metadata of a translation key, this file descriptor also provides additional context, except it does so for the entire file. Note that you can use any formatting you like for the context string.
{
...
"@@context": "my:cool:context",
...
}
NOTE
This value is currently ignored by both Lyrebird as well as intl
.
Author
Gives credit to (or documents) the author of the file.
{
...
"@@author": "Chuck Norris",
...
}
NOTE
This value is currently ignored by both Lyrebird as well as intl
.