Introduction to Fluent syntax

Basic syntax

A Message represents the basic unit of translation in Fluent. This is the simplest form messages can take:

welcome-msg = Hello, World!

welcome-msg is the message identifier (or message ID), and Hello, World! is the value of this message.

Messages can contain variables, for example:

welcome-msg = Welcome { $user }

$user is the name of the variable, and should never be translated. The fragment included between curly braces is called a placeable, and can be moved within the text. For example, the message above would be translated in Italian as follows, leaving the placeable unchanged:

welcome-msg = Benvenuto { $user }

Messages can also reference other messages. In this case, the placeable won’t include the $ sign, simply the message identifier:

menu-settings = Settings
help-menu-settings = Click { menu-settings } to save the file.

A Term is a special category of Message:

  • Terms have identifiers starting with a dash, e.g. -brand-short-name.
  • They can’t be called directly in the source code, but only referenced within other messages.
  • Each localization can add or remove attributes, regardless of the reference language (typically en-US).

For example, a term is used to define the Firefox brand name. For more details, see this document.

-brand-short-name = Firefox
close-msg = Close { -brand-short-name }

Attributes

For a single HTML object there might be multiple messages to localize: for example, a button could have a label and an accesskey, a link could have a text but also a tooltip. Such objects can be localized defining multiple attributes within a single message. For example, in the case of a button:

login-button =
    .label = Login
    .accesskey = L

The message login-button doesn’t have a value, but has 2 attributes defined: label and accesskey. There are a few things to highlight in this FTL fragment:

  • Attribute definitions must be indented and start with a period.
  • There is an equal sign following the message identifier, even if there is no value (i.e. the value is Null).
  • All attributes of messages found in the reference language – typically en-US – must be present in the localization. This will be enforced by tools like compare-locales, which will warn on missing or obsolete (only available in the localization) attributes.
instructions-link = Log out
    .tooltip = Disconnect from this account

In this case, instructions-link has a value (Log out) and a tooltip attribute (Disconnect from this account).

It’s important to note that, in FTL files, indentation is part of the syntax; indenting elements incorrectly will lead to parsing errors.

Selectors and plurals

With the select expression, a single message can provide several alternatives. The selected value will depend on the value of a variable, another message attribute, or a function.

The most common use of select expressions is for plural forms:

emails = { $unreadEmails ->
        [one] You have one unread email.
       *[other] You have { $unreadEmails } unread emails.
    }

Notice that both the variants and the closing curly brace are indented. The same message can also be written as:

emails =
    { $unreadEmails ->
        [one] You have one unread email.
       *[other] You have { $unreadEmails } unread emails.
    }

One of the variants starts with a *: that indicates the default option, and it must always be defined in a select expression. The part before -> is called the selector.

In this case, the message displayed will change based on the numeric value of $unreadEmails. For plurals, the variant key can either be a perfect match to a number or one of the CLDR plural categories. This allows to define special cases, beyond the number of plurals expected for a language:

emails = { $unreadEmails ->
        [one] You have one unread email.
        [42] You have { $unreadEmails } unread emails. So Long, and Thanks for All the Fish.
       *[other] You have { $unreadEmails } unread emails.
    }

Note how the [one] form in English doesn’t explicitly use the variable, in order to display the word one instead of the digit 1.

In plural messages is always possible to expose the number, even if the reference language doesn’t; the name of the variable is defined at the beginning of the select expression (in the selector), in this case $unreadEmails. The first example in this section can be translated in Italian using $unreadEmails in both forms:

emails = { $unreadEmails ->
        [one] C’è { $unreadEmails } messaggio non letto.
       *[other] Ci sono { $unreadEmails } messaggi non letti.
    }

Terms and parameterized terms

As described at the beginning of the document, terms are a special type of messages. They are used to define translations of common words and phrases, which can then be used inside of other messages. They can be recognized because of the identifier starting with a dash, e.g. -brand-short-name. Terms can also define additional language-specific attributes which are not present in the reference language (typically en-US).

While in most cases terms will have a single value, it’s also possible to define multiple values associated controlled by a parameter. These parameterized values can be used to define grammatical cases or any other language-specific modifications of the value required by the grammar of the spelling rules. When referencing a term from another message, you can optionally specify a parameter and its value with the -term-identifier(parameter: "value") syntax.

Consider the following example in English:

-fxaccount-brand-name = Firefox Account

sync-signedout-account-title = Connect with a { -fxaccount-brand-name }

In Italian this can become:

-fxaccount-brand-name =
    { $capitalization ->
        [lowercase] account Firefox
       *[uppercase] Account Firefox
    }

sync-signedout-account-title =
  Connetti il tuo { -fxaccount-brand-name(capitalization: "lowercase") }

Similar to select expressions, you must define a default value, identified by *. Also notice that parameter names are arbitrary, and don’t need to be in English. To get the default value (uppercase in the example), the term can be called without any parameter ({ -fxaccount-brand-name } or { -fxaccount-brand-name() }).

Parameters can also be nested for more complex situations, for example:

-sync-brand-short-name =
    { $case ->
        *[nom] { $capitalization ->
           *[upper] Синхронізація
            [lower] синхронізація
        }
        [gen] { $capitalization ->
           *[upper] Синхронізації
            [lower] синхронізації
        }
        [dat] { $capitalization ->
           *[upper] Синхронізації
            [lower] синхронізації
        }
        [acc] { $capitalization ->
           *[upper] Синхронізацію
            [lower] синхронізацію
        }
        [abl] { $capitalization ->
           *[upper] Синхронізацією
            [lower] синхронізацією
        }
    }

use-firefox-sync =
    Підказка: При цьому використовуються окремі профілі. Скористайтеся
    { -sync-brand-short-name(case: "abl", capitalization: "lower") }
    для обміну даними між ними.

Again, not all parameters need to be specified at all times when referencing a term. If some (or all) parameters are omitted, the corresponding default values will be used. In the example above, { -sync-brand-short-name(case: "abl") } will result in the uppercase Синхронізацією translation because upper is defined as the default value of the ablative case.