Skip to content
On this page

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.

JSON
{
    "@@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.

JSON
{
    ...
    "myPageTitle": "My Awesome Page",
    ...
}

Newlines

As is expected in JSON-formatted strings, we can use \n to represent line breaks in our translation strings.

JSON
{
    ...
    "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.

JSON
{
    ...
    "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.

JSON
{
    "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.

JSON
{
    "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.

JSON
{
    "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.

JSON
{
    "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.

JSON
{
    ...
    "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.

JSON
{
    ...
    "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.

JSON
{
    ...
    "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.

JSON
{
    "@@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.

JSON
{
    ...
    "@@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.

JSON
{
    ...
    "@@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.

JSON
{
    ...
    "@@author": "Chuck Norris",
    ...
}

NOTE

This value is currently ignored by both Lyrebird as well as intl.