Localizing

Localizing the Firefox OS Boilerplate App

As Firefox OS devices are launched in more and more countries and apps become available to users of all different languages, it becomes increasingly important to consider localizing your app. Making your app available in more languages is one of the best ways to make your app available and relevant to more users.

As such, we’re piloting a program to help developers not only localize their apps, but also connect them with localizers. If you’re an interested developer or localizer, please get in touch and we’ll tell you more about how to join our program.

Localizing is generally a straight-forward process, and consists of two main steps:

  1. preparing your app for localization (sometimes called internationalization)
  2. obtaining translations of relevant app content

There are many approaches to localization. Many of you are probably already familiar with gettext-based libraries. Jed is one such implementation specifically designed for html and JavaScript-based applications. Mozilla is also leading a next-generation localization project called L20n. In this article, we build upon the method we previously mentioned using the webl10n.js library also employed by Gaia, the UI layer for Firefox OS and show you how we localized the Firefox OS Boilerplate App.

Firefox OS Boilerplate App, localized in French
Firefox OS Boilerplate App, localized in French

Setup

If you’d like to follow along, clone the Firefox OS Boilerplate App, which is available on Github:

clone https://github.com/robnyman/Firefox-OS-Boilerplate-App.git

As mentioned, we used the version of Fabien Cazenave’s webL10n.js library that is available in the Gaia source tree (direct link). In your own projects, feel free to use either version. Not sure which to use? Read this.

First, I navigated to the directory that contains all application JavaScript and then download l10n.js:

[Firefox OS App Boilerplate/js]$
wget https://github.com/mozilla-b2g/gaia/blob/master/shared/js/l10n.js

Then I created a directory to contain all locale information:

[Firefox OS App Boilerplate/]$
mkdir locales

This is where we store files that contain both the base, and the translated strings for the Boilerplate App.

Now, in that directory, I created the locales initialization file. You can name this anything you like. I selected locales.ini, which is what many of the Gaia apps use:

[Firefox OS App Boilerplate/locales]$
touch locales.ini

In that file, I began by specifying which property file to import for the default language, which in our case is en-US:

@import url(en-us/app.properties)

The import line for your default locale should always be the first line of this file.

It’s up to you how to name and organize your properties files. For the Firefox OS Boilerplate, I chose to have each property file be named app.properties and be stored in its own directory, name for its locale. Here’s what the beginning of the locales/ directory looks like:

??? ar
?   ??? app.properties
?   ??? manifest.properties
??? de
?   ??? app.properties
?   ??? manifest.properties
??? el
?   ??? manifest.properties
??? en-US
?   ??? app.properties
?   ??? manifest.properties

This structure makes working with our chosen translation platform, Transifex, easier. We’ll explain Transifex along with those manifest.properties files a bit later on.

As new locales are added, the locales.ini file needs to be updated. Here are lines I added for the ar and de locales:

[ar]
@import url(ar/app.properties)
[de]
@import url(de/app.properties)

Each import statement needs to be preceded by a heading with the relevant locale code in square brackets. This tells l10n.js which import statements to use for which locales.

Next, I edited the main app file, index.html, to include the l10n.js library and to specify which initialization file to use. In the head section, I added:

<!-- Localization -->
<link rel="resource" type="application/l10n" href="locales/locales.ini" />
<script type="application/javascript" src="js/l10n.js"></script>

At this point I checked to make sure everything was still loading as expected, which it was. The easiest way to check your work as you go is to use the Firefox OS Simulator.

Indicating translatable content with data-l10n-id tags

One advantage of using l10n.js is that any elements specified as needing translation are translated automatically upon page load without the user having to select a locale. The library determines the locale based on the system locale (e.g., which locale the user selected when they completed the first run experience).

The way that you specify which elements should be translated is by giving them data-l10n-id attributes. For example, I indicated that the h1 heading "Firefox OS Boilerplate App" needed translation with the following:

<h2 data-l10n-id="app-heading">
    Firefox OS Boilerplate App
</h2>

The value that you give this data-l10n-id attribute will become a key in your properties file. In en-us/app.properties, I added a corresponding line for this heading:

app-heading  = Firefox OS Boilerplate App

For your default locale, the value assigned to this key will be identical to what it is in your app. But for other locales, it will be translated. Here’s what the corresponding line looks lie for de/app.properties:

app-heading  = Firefox OS Boilerplate App in Deutsch!

Any element with text that you want to localize can and should be given an data-l10n-id attribute and then a corresponding line should be added to your default locale properties file. This can be tedious to do after you have created a lot of application code, so you might consider writing a script to help you out. (I have one in the works, but it’s not fit for consumption yet).

Retrieving translated strings with JavaScript

Sometimes you will need to access localized strings within JavaScript. When you do this, in order to be sure the L10n.js library is available, you’ll need to wrap your functions with the the navigator.mozL10n.ready() function. The following code snippet enables the localization of a notification that is created via JavaScript:

navigator.mozL10n.ready ( function () {
    // grab l10n object
    var _ = navigator.mozL10n.get;
    // Notifications
    var addNotification = document.querySelector("#add-notification");
    if (addNotification) {
        addNotification.onclick = function () {
            var appRef = navigator.mozApps.getSelf(),
                icon;
            appRef.onsuccess = function (evt) {
                icon = appRef.result.manifest.icons["16"];
                console.log("I am: " + icon);
                var notification = navigator.mozNotification.createNotification(
                    _('notification-text'),
                    icon,
                    icon
                );
                notification.show();
                var img = document.createElement("img");
                img.src = icon;
                document.body.appendChild(img);
            };
        };
    }
});

If you don’t follow this method, you can’t be sure that your JavaScript will be parsed after the l10n library is completely initialized.

Localizing the manifest

After tagging all elements with data-l10n-id attributes and adding corresponding lines to my en-us/app.properties file, it was then time to make some changes to the manifest.webapp.

The first change was to make sure a default locale was specified:

"default_locale": "en",

This property refers to the locale of the manifest itself. It is used primarily by the Marketplace to determine how to parse the manifest. It is not used by the device the app is installed on or the Firefox OS simulator. MDN recommends that whatever locale is used here is not included in the ‘locales’ property. Instead, the name and description for the default locale is specified in ‘name’ and ‘description’ fields which are always required.

Next, I added a ‘locales’ property:

"locales": {
}

As new locales are added, it’s necessary to update the locales property with a localized version of the app name and description. These values are used not only by the Firefox OS user interface, but also the Firefox Marketplace. To accomplish this, I created separate manifest.properties files in each of my locales directories. Having separate files for each locales makes it easier for localizers to work on the project and also makes it easier for me to manage. When a localizer completes a new locale, I copy the values to the manifest.webapp file. This is something that could be easily scripted, however.

This is the translated de/manifest.properties file:

name=Firefox OS Boilerplate App in Deutsch!
description=Boilerplate Firefox OS App mit Beispiel Anwendungsfälle, um loszulegen

And the updated ‘locales’ property in manifeset.webapp:

"locales": {
  "de": {
    "name": "Firefox OS Boilerplate App",
    "description": "Boilerplate Firefox OS App mit Beispiel Anwendungsfälle, um loszulegen"
  }
}

Managing Localization Process with Transifex

With the Boilerplate, we started a pilot program to explore the use of Transifex for managing translations. If you visit our team page there, you’ll see the Boilerplate along with a handful of other apps from developers who have joined us there.

Why Transifex?

In looking at l10n platforms, we wanted one that would support both paid and volunteer translations as well as one that would support a wide range of localization formats and workflows. Transifex fit that profile. We’re also very excited about Pontoon a platform currently in development by our l10n team and look forward to using with the Boilerplate and other apps when it’s ready.

Creating the project

Once signed up and logged in to Transifex, creating a project is easy. You specify a project name, description, source language and license. If you indicate that your license is open source, you’ll be prompted for the link to your app’s source code.


Configuring Transifex (tx config)

I like to work from the command line, so I used the Transifex client (installation details) for the following steps, but you can do the following steps from the Transifex website as well.

Working in the root of my Firefox OS Boilerplate App directory, I first initiated the Transifex project:

[Firefox OS App Boilerplate/]$
tx init

This command creates a .tx directory and a config file within it.

When first setting up a project to work with Transifex, you’ll need to set some values in this config file.

The .tx/config file for the Boilerplate looks like this:

[main]
host = https://www.transifex.com
 
[firefox-os-boilerplate.app_properties]
file_filter = locales/<lang>/app.properties
source_file = locales/en-US/app.properties
source_lang = en
type = MOZILLAPROPERTIES
minimum_perc = 50
 
[firefox-os-boilerplate.manifest_properties]
file_filter = locales/<lang>/manifest.properties
source_file = locales/en-US/manifest.properties
source_lang = en
type = MOZILLAPROPERTIES
minimum_perc = 50

Each block of the config is indicted with square brackets. A project on Transifex can have any number of resources, so you can organize your app in the way that you like.

For the Boilerplate, we have two resources:

  • firefox-os-boilerplate.app_properties, which maps to the file app.properties and includes all of the strings from the app that we want to localize.
  • firefox-os-boilerplate.manifest_properties, which maps to the file manifest.properties and includes the localize name and description that we’ll copy to the manifest.webapp

Resources are also listed in the Transifex web interface:


In Transifex, each resource will be copied when a new language is requested. Translators then check out those files, edit them to include their translations and then check them back in when they are done.

Other options are:

  • file_filter: Tells Transifex how you have organized your locale files. For the boilerplate, I wanted each property file to have the same name and be sorted into directories named after each locale. Transifex substitues locale for to acheive this.
  • source_file: Tells transifex what is the source of strings (default locale).
  • source_lang: Indicates the locale of the source file (e.g the default project locale).
  • type: Indicates what file type you are using for translation. Transifex supports a number of options.
  • minimum_perc: Sets the threshold value for when Transifex will pull in a new locale. 50 means that a locale must be at least 50% complete before Transifex will pull the locale into your project.

Once the config file was completed, I pushed it to Transifex with:

[Firefox OS App Boilerplate/]$
tx push -s
Pushing translations for resource firefox-os-boilerplate.app_properties:
Pushing source file (locales/en-US/app.properties)
Resource does not exist.  Creating...
Pushing translations for resource firefox-os-boilerplate.manifest_properties:
Pushing source file (locales/en-US/manifest.properties)
Resource does not exist.  Creating...
Done.

Workflow

Once the Boilerplate project was setup to use Transifex, I was able to use the client to pull and push new / updated translations:

[Firefox OS App Boilerplate/]$
tx pull
Pulling translations for resource firefox-os-boilerplate.app_properties (source: locales/en-US/app.properties)
 -> ar: locales/ar/app.properties
 -> id: locales/id/app.properties
Pulling translations for resource firefox-os-boilerplate.manifest_properties (source: locales/en-US/manifest.properties)
Done.

Transifex works seemlessly with any revision control system. We use git for the Firefox OS Boilerplate, and our workflow looks like:

  1. Use tx pull to bring in new translations from Transifex (could also download them via the web interface).
  2. Commit and push changes vwith git.
  3. Repeat.

We can also accept localizations via git and them push them to Transifex.

Note: When using Transifex, I recommend that you keep your .tx config directory in your project’s code repo. You’ll want anyone checking out your project to use this information to properly sync with Transifex. No secret information is contained in .tx/config (rather, that’s in ~/.transifexrc).

Invitation to participate!

If you are a developer interested in localizing your app, or a localizer interested in contributing translations, we’d love to hear from you! We also invite you to join our team on Transifex if you’d like to connect with other developers and translators who are working on localization of Firefox OS apps.

View full post on Mozilla Hacks – the Web developer blog

VN:F [1.9.22_1171]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.22_1171]
Rating: 0 (from 0 votes)

Localizing Firefox OS Apps

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.

In this post, we will discuss how to localize your Firefox OS app using the libraries currently employed for localization in the Gaia layer of Firefox OS. 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. However, if you decide to use this approach, bear in mind that some features of the library (for example l10n_date.js uses non-standard toLocaleFormat method) are not cross-browser compatible, and know Gaia will likely move to L20n at some point in the future.

L10n.js

Currently Firefox OS Gaia uses a modified version of Fabien Cazenave’s L10n.js library to localize the default Apps that are available in Firefox OS. It is available in the Gaia source tree. The library relies on the key/value based properties format. The L10n.js parser also supports import rules that can be used for client side language selection. The default Gaia apps use an ini file to specify the import statements and a link tag to load up the ini file.

As an example of how it works, examine the Bluetooth App, which can be used to transfer files. The App’s properties files are structured in the following fashion.
bluetooth
This App contains properties files for four locales (ar, en-US, fr, and zh-TW). A portion of the 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 with the set of strings to localize. All of these files are stored in the locales directory of the Bluetooth App. In addition, this folder contains a locales.ini file with the following content:

@import url(bluetooth.en-US.properties)
 
[ar]
@import url(bluetooth.ar.properties)
 
[fr]
@import url(bluetooth.fr.properties)
 
[zh-TW]
@import url(bluetooth.zh-TW.properties)

The ini file specifies what properties files to load based on the locale of the App user. It acts as multi-locale dictionary listing all supported locales. Also, in this particular instance, the en-US properties file acts as the default locale if no entry is found for a given user locale. This ini file is loaded by using the following syntax with the link tag.

<link rel="resource" type="application/l10n" href="locales/locales.ini" />

The rel attribute can also be set to “prefetch” to preload the ini file to increase performance.

L10n Element Attributes

You define elements that need translation by adding 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 attribute “label1” serves as the key into the properties file. Complex strings can be created in the properties files using argument substitution and the plural macro.

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:

loginmessage = 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 by using the HTML below.

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

Plural macro

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:

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": "1" }'>Mail Message</h3>

L10n Script

Most of the default Firefox OS Apps use a script tag 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 source attribute.

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

The most useful methods and properties for mozL10n are described below.

The get Method

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 the properties file.

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 localize Method

The localize method can be used to add the L10n attributes to dynamically created content. The method takes an element parameter, and id parameter, and an optional args parameter. The element parameter specifies the element to be localized. The id parameter specifies the L10n-based attribute id you want to assign to the element. The optional args parameters allows you to create the data-l10n-args attribute and set its JSON value.

var button2 = document.querySelector("#button2");
if (button2) { 
    button2.onclick = function () {
        var myElement = document.createElement('span');
        var lblTxt =document.createTextNode("My Label");
        myElement.appendChild( lblTxt );
        navigator.mozL10n.localize(myElement, 'label3'{ arg: "myarg" });
        document.body.appendChild(myElement);
    }
};

This will create the following HTML.

<span data-l10n-id="label3" data-l10n-args="{"arg":"myarg"}"> My Label</span>

In addition the text will also be translated immediately.

The ready method

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

var button1 = document.querySelector("#button1");
if (button1) { 
    button1.onclick = function () {                
        navigator.mozL10n.language.code = "fr";
        navigator.mozL10n.ready( function() {
            alert(navigator.mozL10n.get("button1"));
        });        
    }
};

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";
navaigator.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. The library 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 properties files and a date.ini file to import the locale specific properties file. The date.ini file is located in the Gaia source tree and the locale specific properties files are located in https://github.com/mozilla-b2g/gaia/tree/master/shared/locales/date directory. These properties files define formats and strings for things like the day a week starts on, short date formats and then names of months. To use this library in your own application, you will need to copy all of the files to your specific project. The ini and script files are loaded in a similar manner to the L10n.js library.

<link rel="resource" type="application/l10n" href="locales/date.ini" />
<script defer src="js/l10n_date.js"></script>

When using the L10n_date library’s format methods, the strings in the properties files can use standard C++ date/time formatting codes. As an example of this, examine the Gaia Clock App’s properties 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 properties 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:

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 of 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 rerturns
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 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 the Mozilla Developer Network article, “Creating localizable web applications.”

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

View full post on Mozilla Hacks – the Web developer blog

VN:F [1.9.22_1171]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.22_1171]
Rating: 0 (from 0 votes)