1 of 32

i18n

Overview about the current state & developer experience using i18n in Javascript

2 of 32

  • Drivers
    • Context

  • Overview
    • i18n Libraries

  • Developer Experience
    • Needs and concerns

Index

  • How they do
    • Android
    • iOS

  • Extras
    • links

  • What we can do ?

3 of 32

Drivers

| > Context

4 of 32

Drivers | Context

In order to have a more inclusive, universal and accessible world , everybody should have the right to access and to be informed equally.

The internet. Platform used by billions should provide the mechanism to make easy people from different backgrounds , speaking different languages to understand the content of the websites and from all the internet.

5 of 32

Overview

| > i18n Libraries

Analysis of the most popular i18n libraries in npm and libraries mentioned in : https://github.com/tc39/ecma402/issues/92

6 of 32

7 of 32

8 of 32

i18next

  • Detect the user language
  • Load the translations
  • Optionally cache the translations
  • Extensible by their plugin system
  • Translation functions like interpolation, formatting and plurals.

9 of 32

{

"button": {

"save": "save {{count}} change",

"save_plural": "save {{count}} changes"

}}

{

"title": "{{what}} in english"

}

Allows nested objects and arrays

Plurals

Interpolation

{

"friend_male": "A boyfriend",

"friend_female": "A girlfriend",

"friend_male_plural": "{{count}} boyfriends",

"friend_female_plural": "{{count}} girlfriends"

}

i18next.init({

lng: 'de',

resources: {

// or load from file/server, etc ...

de: {

translation: {"hello world": "hallo Welt"}}

}

});​

// Nesting Object and Arrays

i18next.t('button', { returnObjects: true, count: 1 });

i18next.t('friend'); // -> "A friend"

i18next.t('friend', { context: 'male' }); // -> "A boyfriend"

i18next.t('friend', { context: 'female' }); // -> "A girlfriend"

i18next.t('friend', {context: 'male', count: 100}); // -> "100 boyfriends"

Javascript basic API

10 of 32

i18n

  • Supports node express
  • Supports different template engines.

11 of 32

i18n

{

"Hello": "Hello",

"Hello %s, how are you today?": "Hello %s, how are you today?",

"weekend": "weekend",

"Hello %s, how are you today? How was your %s.": "Hello %s, how are you today? How was your %s.",

"Hi": "Hi",

"Howdy": "Howdy",

"%s cat": {

"one": "%s cat",

"other": "%s cats"

},

"tree": "tree",

"%s dog": {

"one": "one dog",

"other": "[0] no dog|[2,5] some dogs|[6,11] many dogs|[12,36] dozens of dogs|a horde of %s dogs"

}

}

Keys

// Library works with mustache style template systems

app.use(function (req, res, next) {

// mustache helper

res.locals.__ = function () {

return function (text, render) {

return i18n.__.apply(req, arguments);

};

};

[...]

next();

});

Basic API for express server

12 of 32

polyglot

  • Browser and Node environments
  • Offers interpolation and pluralization.

13 of 32

polyglot.extend({

"nav": {

"hello": "Hello",

"hello_name": "Hello, %{name}",

"sidebar": {

"welcome": "Welcome"

}

}

});

polyglot.t("nav.sidebar.welcome");

=> "Welcome"

Basic API usage

polyglot.extend({

"num_cars": "%{smart_count} car |||| %{smart_count} cars",

});

polyglot.t("num_cars", {smart_count: 0});

=> "0 cars"

polyglot.t("num_cars", {smart_count: 1});

=> "1 car"

polyglot.t("num_cars", {smart_count: 2});

=> "2 cars"

Plurals

14 of 32

messageformat

  • Handles arbitrary nesting of pluralization and select rules
  • Supports all ~466 languages included in the Unicode CLDR
  • Works on the server and the client
  • Remarkably useful even for single-language use
  • Speed & efficiency: Can pre-compile messages to JavaScript code
    • Great for speed: message formatting is just string concatenation
  • Compatible with other MessageFormat implementations
  • Extendable with custom formatting functions
  • Very white space tolerant
  • Supports Unicode, including RTL and mixed LTR/RTL strings

15 of 32

import Messages from 'messageformat/messages'

import msgData from './messages.js or json'

const messages = new Messages(msgData, 'en') // sets default locale

messages.get('b', { COUNT: 3 }) // 'This has 3 users.'

messages.get(['c', 'd'], { P: 0.314 }) // 'We have 31% code coverage.'

messages.get('e') // 'e'

messages.setFallback('en', ['foo', 'fi'])

messages.get('e') // 'Minä puhun vain suomea.'

messages.locale = 'fi'

messages.get('b', { COUNT: 3 }) // 'Tällä on 3 käyttäjää.'

messages.get('c').d({ P: 0.628 }) // 'We have 63% code coverage.'

API Usage

{

"en": {

"a": "A {TYPE} example.",

"b": "This has {COUNT, plural, one{one user} other{# users}}.",

"c": {

"d": "We have {P, number, percent} code coverage."

}

},

"fi": {

"b": "Tällä on {COUNT, plural, one{yksi käyttäjä} other{# käyttäjää}}.",

"e": "Minä puhun vain suomea."

}

}

Keys

16 of 32

intl-messageformat

  • FormatJS is aligned with: ECMAScript Internationalization API (ECMA-402), Unicode CLDR, and ICU Message syntax

  • Offers interpolation and pluralization.

17 of 32

var output;

var enNumPhotos = new IntlMessageFormat(MESSAGES['en-US'].NUM_PHOTOS, 'en-US');

output = enNumPhotos.format({numPhotos: 1000});

console.log(output); // => "You have 1,000 photos."

var esNumPhotos = new IntlMessageFormat(MESSAGES['es-MX'].NUM_PHOTOS, 'es-MX');

output = esNumPhotos.format({numPhotos: 1000});

console.log(output); // => "Usted tiene 1,000 fotos."

API Basic Usage

var MESSAGES = {

'en-US': {

NUM_PHOTOS: 'You have {numPhotos, plural, ' +

'=0 {no photos.}' +

'=1 {one photo.}' +

'other {# photos.}}'

},

'es-MX': {

NUM_PHOTOS: 'Usted {numPhotos, plural, ' +

'=0 {no tiene fotos.}' +

'=1 {tiene una foto.}' +

'other {tiene # fotos.}}'

}

};

Keys

18 of 32

fluent

  • Client- and server-side.

  • Compatible with ICU and ECMA 402

  • Date, time, and number formatting. Plural categories. Full bidirectionality support. Custom formatters. Easy-to-read syntax.

19 of 32

import { FluentBundle, ftl } from 'fluent';

const bundle = new FluentBundle('en-US');

const errors = bundle.addMessages(ftl`

-brand-name = Foo 3000

welcome = Welcome, { $name }, to { -brand-name }!

`);

if (errors.length) {

// syntax errors are per-message and don't break the whole resource

}

const welcome = bundle.getMessage('welcome');

bundle.format(welcome, { name: 'Anna' });

// → 'Welcome, Anna, to Foo 3000!'

JS Basic usage

//.ftl file

proceed-button =

.label = Do you want to proceed?

.action-ok = Yes

.action-cancel = No

Keys

20 of 32

fbt

  • FBT works by transforming your <fbt> and fbt(...) constructs via Babel plugins.

  • Translate payloads generated at build-time.

  • FBT creates references of all possible variations for the given fbt phrase and accesses this at runtime.

21 of 32

<button>

<fbt desc="Canonical intro text">

Hello, World!

</fbt>

</button>

Inline Usage

{

"fb-locale": "es_LA",

"translations": {

"JBhJwfCe2TutVvTr9c9HLw==": {

"tokens": [],

"types": [],

"translations": [

{

"translation": "nombre",

"variations": {}

}

]

}...

Keys (Auto generated)

More code examples :

22 of 32

format-message

  • Supports html like tags
  • Supports message descriptions for translators

23 of 32

formatMessage.setup({

locale: "en",

translations: {

en: {

foo: "foo"

}

}

})

formatMessage("foo") // => "foo"

formatMessage('Hello, { name }!', { name: user.name })

JS Basic usage

formatMessage(`{

gender, select,

male {His inbox}

female {Her inbox}

other {Their inbox}

}`, { gender: user.gender })

formatMessage(`{

count, plural,

=0 {No unread messages}

one {# unread message}

other {# unread messages}

}`, { count: messages.unreadCount })

// Message descriptions

formatMessage({

id: 'update_action_button',

default: 'Update',

description: 'Text displayed on the update resource button to trigger the update process'

})

Keys

24 of 32

  • Message/plural format library with locale support.

  • Subset of the ICU MessageFormatSyntax

  • Support fallback strategy progressive localization

Closure Library

I see {NUM_PEOPLE, plural, offset:1

=0 {no one at all}

=1 {{WHO}}

one {{WHO} and one other person}

other {{WHO} and # other people}}

in {PLACE}.

Calling format({'NUM_PEOPLE': 2, 'WHO': 'Mark', 'PLACE': 'Athens'}) would

produce "I see Mark and one other person in Athens." as output.

{NUM_FLOOR, selectordinal,

one {Take the elevator to the #st floor.}

two {Take the elevator to the #nd floor.}

few {Take the elevator to the #rd floor.}

other {Take the elevator to the #th floor.}}

Calling format({'NUM_FLOOR': 22}) would produce

"Take the elevator to the 22nd floor".

var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'});

Basic Usage

25 of 32

Developer Experience

| > Needs and Concerns

Analysis of interview results, asked random developers about what they will have as main requirements for i18n implementation, using MoSCoW method as prioritization technique

26 of 32

Requirements (Should)

Must Have

Should Have

Could Have

Won't Have

Use JSON and JS formats

Customize or have more flexibility in “Select”

Messages should have more context “description” or me

Dedupe translation keys(Manage and Merge)

Works in all platforms

Support export and import from .po or xliff formats

Minimal easy to use API

Cache

Compatible with my actual projects

27 of 32

How they do

| > Android

| > iOS

28 of 32

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">

<!-- Example placeholder for a special unicode symbol -->

<string name="star_rating">Check out our 5

<xliff:g id="star">\u2605</xliff:g>

</string>

<!-- Example placeholder for a for a URL -->

<string name="app_homeurl">

Visit us at <xliff:g

id="application_homepage">http://my/app/home.html</xliff:g>

</string>

<!-- Example placeholder for a name -->

<string name="prod_name">

Learn more at <xliff:g id="prod_gamegroup">Game Group</xliff:g>

</string>

...

</resources>

Example of i18 Keys in using xliff format in Android

29 of 32

let alertTitle = NSLocalizedString("Welcome", comment: "")

let alertMessage = NSLocalizedString("Thank you for trying this app, you are a great person!", comment: "")

let cancelButtonText = NSLocalizedString("Cancel", comment: "")

let signupButtonText = NSLocalizedString("Signup", comment: "")

let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: UIAlertControllerStyle.Alert)

let cancelAction = UIAlertAction(title: cancelButtonText, style: UIAlertActionStyle.Cancel, handler: nil)

let signupAction = UIAlertAction(title: signupButtonText, style: UIAlertActionStyle.Default, handler: nil)

alert.addAction(cancelAction)

alert.addAction(signupAction)

presentViewController(alert, animated: true, completion: nil)

Other API’s

Example of i18 implementation in iOS

30 of 32

Extras

| > Links

31 of 32

Translators tools to help i18n

32 of 32