Mozilla's getting a new look. What do you think? https://mzl.la/brandsurvey

Internationalization helpers: IntlHelper and mozIntl

Draft
This page is not complete.

This article looks at how Firefox OS handles localization of dates, times, numbers and collators from version 2.5 onwards, using the Internationalization API and Gaia's built in helpers, IntlHelper and mozIntl.

The Internationalization API

The Internationalization API (Intl API) is a built-in JavaScript feature that has been supported in modern browsers for some time now. It provides a standard approach for implementing locale-aware dates, times, etc. Let's look at a simple example to show typical usage — here we use the toLocaleString() method on the Date object to return a locale dependant day and month:

var date = new Date();
date.toLocaleString(navigator.languages, {
  day: 'numeric',
  month: 'numeric'
});

And here we use mozHour12 to return locale-dependant 12 hours times (this currently requires adding date_time_helper.js to your page — we'll move this functionality into Gecko soon):

date.toLocaleString(navigator.languages, {
  hour12: navigator.mozHour12,
  hour: 'numeric',
  minute: 'numeric'
});

The final example shows how to create a more general localizable 2 decimal place value:

value.toLocaleString(navigator.languages, {
  style: 'decimal',
  useGrouping: false,
  minimumIntegerDigits: 2
});

We are using some common patterns across all our future localization code in Gaia — these can be considered best practices to use in third party apps too:

  • Using navigator.languages for the first argument.
  • Taking hour12 from navigator.mozHour12.
  • Putting every number displayed in the UI through NumberFormat.
  • Putting every date and time displayed in the UI through DateTimeFormat.
  • Putting every sorting and text searching operation performed in an app through Collator.

Issues with the vanilla Intl API

This API is fine, but there are some problems to consider. First, it's not very fast — this method creates a new formatter on every call, which is not good for performance-sensitive code.

To mitigate this, the Intl API provides a way to offload the costly part and create a formatter that you can then use in your calculations:

var formatter = Intl.DateTimeFormat(navigator.languages, {
  day: 'numeric',
  month: 'numeric'
});

for (var i = 0; i <messages.length; i++) {
  messages[i].dateElement.textContent = formatter.format(messages[i].date);
}

Second, it doesn't work well with language changes and time format (12/24h clock) changes on fly. This is where our IntlHelper Gaia library comes in.

IntlHelper

IntlHelper allows you to define and name your internationalization formatters and collators up front — and cache and reuse them; it also enables you to refire formatting/sorting/searching functions whenever your Intl object is affected by some change. The main goal of it is to help developers cache patterns and update when the formatter gets updated (on timeformatchange for time formatters, on languagechange for all formatters, probably others in the future.)

To use it, you can follow a pattern similar to this:

IntlHelper.define('shortDate', 'datetime', {
  day: 'numeric',
  month: 'numeric'
});

function setValue() {
  const formatter = IntlHelper.get('shortDate');
  element.textContent = formatter.format(currentDate);
}

setValue();

IntlHelper.observe('shortDate', setValue); // first argument may be an array of multiple names if setValue reformats multiple

Including the observer means that any time the formatter has to be recreated, setValue() will be fired, recreating it automatically.

Here's another, slightly more involved example:

IntlHelper.define('digit-padding', 'number', {
  style: 'decimal',
  useGrouping: false,
  minimumIntegerDigits: 2
});

IntlHelper.define('shorttime', 'datetime', {
  hour: 'numeric',
  minute: 'numeric'
});

function setIntlValues() {
  var numFormatter = IntlHelper.get('digit-padding');
  var value = minElem.getAttribute('data-value');

  minElem.setAttribute('data-l10n-args', JSON.stringify({minutes: numFormatter.format(value)}));

  var timeFormatter = IntlHelper.get('shorttime');

  timeElem.textContent = timeFormatter.format(alarm.startTime);
}

IntlHelper.observe(['shorttime', 'digit-padding'], setIntlValues);

IntlHelper can also handle time formatting automatically, for example:

IntlHelper.define('shortTime', 'datetime', {
  hour: 'numeric',
});

function setValue() {
  const formatter = IntlHelper.get('shortTime');
  element.textContent = formatter.format(currentTime);
}

setValue();

IntlHelper.observe('shortTime', setValue);

In this case Gaia will recognize that the formatter uses hour and fire the setValue() callback when the time format changes and add it to the DateTimeFormat() options for you.

Note: In the future we'll also handle pseudo-locales in IntlHelper.

mozIntl

mozIntl is an extension of the Intl API — it provides features that are under consideration for adding to the Intl API, but that we want to centralize and use ahead of time.

Bear in mind that for successful localization, properties will need to be defined as expected (see shared/locales/date/date.en-US.properties for an example) to work, but mozIntl does provide extended features for date, time, duration and list formatting.

This library currently includes five helper methods, documented below.

mozIntl.formatList()

Allows you to format lists according to locale, for example John, Mary, Nick.

In the future we will allow for more informal list formatting such as John, Mary and Nick.

mozIntl.DateTimeFormat()

Extends Intl.DateTimeFormat with two new features:

Dayperiod token removal

mozIntl.DateTimeFormat(navigator.languages, {
  hour12: navigator.mozHour12,
  hour: 'numeric',
  minute: 'numeric',
  dayperiod: false
});

This will advance time without the dayperiod token. This was needed for a number of apps such as Lockscreen and Systemm, but it is not recommended for most code.

Token formatting

var formatter = mozIntl.DateTimeFormat(navigator.languages, {
  hour12: navigator.mozHour12,
  hour: 'numeric',
  minute: 'numeric',
});

formatter.format(date, {
  dayperiod: "<small>$&</small>"
});

This will produce 10:12 <small>am</small>.

It could also be used to create Saturday, July <strong>23</strong>, for example.

mozIntl.calendarInfo()

Returns the firstDayOfTheWeek token. In the future we will add more options such as weekendStarts, weekendEnds and other calendaring information for a given locale.

mozIntl.DurationFormat()

Allows you to format integers with milliseconds into a duration format (like the one the Timer app uses) — hh:mm:ss or mm:ss.SS.

mozIntl.RelativeTimeFormat()

Allows the creation of relative time formats like "5 minutes ago", "in 5 minutes", etc.

Note: The different features are also documented in the source code (linked above), including some useful examples.

Document Tags and Contributors

 Contributors to this page: chrisdavidmills
 Last updated by: chrisdavidmills,