i18n
Overview about the current state & developer experience using i18n in Javascript
Index
Drivers
| > Context
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.
Overview
| > i18n Libraries
Analysis of the most popular i18n libraries in npm and libraries mentioned in : https://github.com/tc39/ecma402/issues/92
i18next
{
"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
i18n
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
polyglot
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
messageformat
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
intl-messageformat
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
fluent
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
fbt
<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 :
format-message
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
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
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
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 | ✅ | | | |
How they do
| > Android
| > iOS
<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
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
Extras
| > Links
Translators tools to help i18n