mozilla

Localizing Firefox OS Apps

In this article, we will discuss how to localize your Firefox OS app using the libraries currently employed for localization in the Gaia layer of Firefox OS, such as L10n.js. Gaia encompasses all of the built-in web apps in the operating system, including the Dialer and the Contacts Manager, so it provides a good model to emulate.

Firefox OS Apps are being used around the world—Spain, Poland, Colombia, and Venezuela with many more countries coming—so it’s important to consider localizing your app from the beginning. But with the open web being as open as it is, there are many frameworks and technologies to pick from when it comes to localization. For example, the Jed Gettext-style library is a popular traditional option.  There are also new localization platforms in development that extend the capabilities available in current libraries. For example, at Mozilla we have a very promising localization project that extends L10n with a variety of compelling new features. To learn more about it, check out the Mozilla Tools group.

Note: If you do decide to use this approach, bear in mind that some features of the library (for example l10n_date.js uses the non-standard toLocaleFormat method) are not cross-browser compatible, and that Gaia will likely move to L20n at some point in the future.

Let's get on and look at localization with L10n.js.

L10n.js

Currently Firefox OS Gaia uses a modified version of the L10n.js library to localize the default apps that are available in Firefox OS. L10n.js is available in the Gaia source tree. The library relies on the key/value based properties format.

As an example of how it works examine the Bluetooth App, which is used to transfer files. The App’s properties files are structured in the following fashion:

This App contains properties files for four locales (ar, en-US, fr, and zh-TW). A portion of the bluetooth.en-US.properties file is listed below:

bluetooth = Bluetooth
confirmation = Confirmation
bluetooth-status-description = Bluetooth is disabled
turn-bluetooth-on = Do you want to turn Bluetooth on?
cancel = Cancel
turn-on = Turn On

As you can see, it’s a simple key/value property file containing the set of strings to localize.

Linking resources

In order for the resources to be used to localize your HTML file, you need to link them from your HTML <head> element:

<link rel="localization" href="locales/bluetooth.{locale}.properties" />

L10n.js uses the URL Template scheme, where the {locale} portion of the path will be replaced with a locale code selected as the result of language negotiation.

On top of that, you need to list all available locales and the default locale to use in your app's manifest.webapp file. First link the file as follows:

<link rel="manifest" href="./manifest.webapp" />

Then add your locales to the manifest. The relevant portions to update are the  default_locale and locales fields, which are fairly intuitive to modify:

"default_locale": "en",
"locales": {
  "es": {
    "name" : "Probador de traducción",
    "description" : "Para ayudar con la localización"
  }
}

Alternatively, you can list available locales and the default locales within the HTML file using <meta> elements:

<meta name="locales" content="en-US, fr, de">
<meta name="default_locale" content="en-US">

L10n.js will do language negotiation between user-selected languages (see NavigatorLanguage.languages) and the list of available languages provided in the manifest, select the language fallback chain and load the resources for the most preferred locale.

L10n Element Attributes

You define elements that need translation by giving them a data-l10n-id attribute, which is the key defined in the properties file. For example, a header that needs localization can look like this:

<h2 data-l10n-id="label1">Label One</h2>

The value of the label1 attribute serves as the key into the properties file. Complex strings can be created in the properties files using argument substitution and the plural macro, both described below. 

The binding between each l10n-id and its associated HTML Element is a one-to-one model. Entities should not be reused because although they may have the same value in your source language, different contexts will often require different translations.

Localizable elements should also not have any content or child elements because the localization will overwrite them.

Argument Substitution

Argument substitution is achieved by surrounding the argument with double curly braces: {{arg}}. A message can then be customized for a specific user using syntax similar to the following:

label1 = Hello {{ user }}, glad you decided to visit

Default values for arguments can be set using the data-l10n-args attribute. This attribute expects a JSON-formatted value. In the above example, a default value could be set for the user argument using the HTML below:

<h2 data-l10n-id="label1" data-l10n-args='{ "user": "Guest" }'></h2>

This will result in the element's content being set as "Hello Guest, glad you decided to visit".

You can also reference other entities in the same way:

brandShortName = Firefox OS
helloWorld = Welcome to {{ brandShortName }}

Plural macro

L10n.js currently supports very limited macro functionality and multi-variant strings.

The plural macro can be used to customize messages based on an argument value. The macro takes a number value and returns zero, one, two, few, many, or other. The return value depends on the value passed in and the current locale’s Unicode Plural Rules. As an example, a customized mail message for the en-US locale may look something like this:

mailMessage = {[ plural(n) ]}
mailMessage[zero]  = you have no messages
mailMessage[one]   = you have one message
mailMessage[two]   = you have two messages
mailMessage[other] = you have {{ n }} messages
<h3 data-l10n-id="mailMessage" data-l10n-args='{ "n": "6" }'></h3>

L10n Script reference

Most of the default Firefox OS apps use a <script> element similar to the following to load the L10n.js library:

<script defer src="/shared/js/l10n.js" charset="utf-8"></script>

To use this library in your own application, you will need to copy the l10n.js file to your local project and change the src attribute to suit.

Once loaded, the L10n.js library will expose a navigator.mozL10n object that can be used for client side localization.

Localizing HTML from JavaScript: Best practice

The best way to localize UI elements from JavaScript is to set the data-l10n-id attribute on an element:

document.getElementById('myelement').setAttribute('data-l10n-id', 'label1');

L10n.js has a MutationObserver set that will react to this change and localize the element. You can also set data-l10n-args, as shown here:

var elem = document.getElementById('myelement');
elem.setAttribute('data-l10n-id', 'label1');
elem.setAttribute('data-l10n-args', JSON.stringify({'name': 'John'}));

Although it may be tempting to use HTMLElement.dataset l10nId or l10nArgs here, we do not recommend doing this. Not only is setAttribute faster, but it also is a more future-proof approach because the data- prefix is used only temporarily and once we move forward with WebAPI standardization effort, data-l10n-id will be replaced with a l10n-id attribute.

The setAttributes Method

The pattern of setting l10n-id and l10n-args is so common that l10n.js provides a helper function to do this:

navigator.mozL10n.setAttributes(elem, 'label1', {'name': 'John'});

The getAttributes Method

In order to retrieve localization attributes, you can use the getAttributes helper:

var l10nAttrs = navigator.mozL10n.getAttributes(elem); // {id: 'label1', args: { 'name': 'John' }}

Removing localization

L10n.js currently does not provide any cleanup function, so when you want to make an element no longer localizable, you need to remove the data-l10n-id attribute and clear the content of your node:

elem.removeAttribute('data-l10n-id');
elem.textContent = '';

The get Method (deprecated)

The get method is used to get a translated string for the current locale. The method takes a key parameter and an optional args parameter. The key parameter specifies the key defined for the properties file.

get is going to be deprecated soon and should be generally avoided. There are currently three use cases which still require this method:

  1. Non HTML UI widgets, like alert and confirm dialogs.
  2. Date/Time formatting
  3. DOM Fragments — when you need to localize a whole block of HTML using a single localization string.

In all other cases, it is much more robust to set l10n-id on an HTML Element:

navigator.mozL10n.get("mylabel")

The args parameter can be used to pass a JSON-formatted argument for strings that contain arguments:

//properties file
welcome = Welcome {{user}}!

//JavaScript
alert( navigator.mozL10n.get(“welcome”,  { user: "Martin" }));

The once method

The once method allows you to define a callback that fires when localization for the current document is complete.

var button1 = document.querySelector("#button1");
button1.setAttribute('data-l10n-id', 'label1');

navigator.mozL10n.once(function() {
  button1.style.display = 'block';
});

Because of the asynchronous nature of localization, the l10n-id may be set on the button before l10n resources are ready, in which case the localization will happen only once the resources are loaded.

once allows you to execute code only when localization is completed and resources are available.

It should guard any code that wants to use the get method, and code that displays localized UI.

The language property

The language property contains a getter and setter for the language code. The language property also contains a getter for the language direction, for supporting right to left (Arabic or Hebrew)  and left to right languages.

var mycode = navigator.mozL10n.language.code;
navigator.mozL10n.language.code = "fr";
navigator.mozL10n.language.direction //returns rtl or ltr

l10n_date Script

For date and time manipulation, Gaia applications extend the capabilities of L10n.js with the l10n_date.js library. As with the l10n.js library, it implements some features that may not be compatible with all browsers. l10n_date.js is available in the Gaia source tree and uses the same property structure described in the L10n.js section of this article. Gaia apps that use this library all rely on a shared set of property files. The locale specific property files are located in the date directory. These property files define formats and strings for items like the day a week starts on, short date formats, and the names of months. To use this library in your own application, you will need to copy all of the files to your specific project.

<link rel="localization" href="locales/date.{locale}.properties" />
<script defer src="js/l10n_date.js"></script>

When using the l10n_date library’s formatting methods, the strings in the property files can use standard C++ date/time formatting codes. As an example of this, examine the Gaia Clock app’s property file. This file contains the following entry for dateFormat for the en-US locale.

dateFormat = %A, %B %e

Using the formatLocale method within the l10n_date library, this will return:

“Full Weekday Name” “Full Month Name” “Day of the Month” (Thursday January 29).

The French version of the property file defines the same key in the following fashion.

dateFormat = %A %e %B

This will produce:

“Full Weekday Name” “Day of the Month” “Full Month” (jeudi 25 janvier).

When you include the l10n_date library, a new method is available to the mozL10n object: navigator.mozL10n.DateTimeFormat(). Once instantiated, this object has several methods that can be used to localize dates. The most useful ones are as follows.

The localeFormat method

The localeFormat method takes a date object parameter and a format pattern parameter and returns the date formatted as specified in the pattern. This method should be used in conjunction with the L10N.js get method to format a localized date.

button3.onclick = function () {
    navigator.mozL10n.language.code = "fr";
    navigator.mozL10n.ready( function() {
        var d = new Date();
        var f = new navigator.mozL10n.DateTimeFormat();
        var format = navigator.mozL10n.get('dateFormat');
        var formatted = f.localeFormat(d, format);
        alert( formatted );
    });    
}

The localeDateString, localeTimeString, and localeString methods

These three methods are just variations on the localeFormat method that return formatted dates based on the following key values within the date properties files:

//en-US
//localeString returns
dateTimeFormat_%c = %a %b %e %Y %I:%M:%S %p
//localeDateString returns
dateTimeFormat_%x = %m/%d/%Y
//localeTimeString returns
dateTimeFormat_%X = %I:%M:%S %p

The fromNow method

The fromNow method takes a date/time parameter and returns a locale-formatted string expressing the difference in time between the current date/time and the date/time passed in. The formatted string will be based on the strings defined in the date properties file. For example:

//Executed on 7/25/2013 12:11:00
var d = new Date("July 25, 2013 11:13:00");
var f = new navigator.mozL10n.DateTimeFormat();
alert( f.fromNow(d));

would alert “58 Minutes Ago” in the en-US locale. The string will be formatted using the minutes-ago-long key in the date properties file.

minutes-ago-long={[ plural(value) ]}
minutes-ago-long[zero] = just now
minutes-ago-long[one] = a minute ago
minutes-ago-long[two] = {{ value }} minutes ago
minutes-ago-long[few] = {{ value }} minutes ago
minutes-ago-long[many] = {{ value }} minutes ago
minutes-ago-long[other] = {{ value }} minutes ago

Learn more, and get involved!

For further reading on good localization practices, see Creating localizable web applications.

And after you’ve finished localizing your own Firefox OS app, why not help with the localization of Firefox OS itself? Take a look at Localizing Firefox OS for more information on how to contribute.

Document Tags and Contributors

Contributors to this page: Sheppy, teoli, gandalf, chrisdavidmills, kscarfone
Last updated by: chrisdavidmills,