Internationalize AngularJS

Lightning Talk

Presented by Rahul Doshi | @doshprompt

from

Architecture

Our solution is based on a simple architecture. We will have a folder for each language that we would like to support. Inside will be localized resource files for each route or module we want to support.

We will also pick a default language that will be used to fall back to the site’s native language if a given user’s language is not supported.

100% of the code is run on the front-end within the browser, and no special server-side support needs to be provided.

The proposed directory structure:

lang
├── en-US
│   ├── common.json
│   ├── login.json
│   ├── home.json
│   └── ...
├── fr-FR
│   ├── common.json
│   ├── login.json
│   ├── home.json
│   └── ...
└── ...

Below is an example of the file format:

{
"cancel": "Cancel",
"no": "No",
"ok": "OK",
"yes": "Yes"
}

The Service

A service will form the core of our localization engine, providing an interface that will be responsible for checking the user's language settings and requesting the appropriate resource based on the language.

It will also provide several methods, a lookup method that will return a localized string for a given key from the loaded resource file, a way to get/set the locale, and finally, a way to setup some defaults.

Lastly, it will notify us (via promises) when an individual file or a group of files has been successfully loaded into memory and is ready for use.

JavaScript:

$scope.strings = {};
locale.ready('common').then(function() {
    $scope.strings.helloWorld = locale.getString('common.helloWorld');
});

HTML:

<p>{{ strings.helloWorld }}</p>
Edit in Plunker

Output:

Hello World!

Implementation: Take Two

Using Fliters:

<p>{{ 'common.helloWorld' | i18n }}</p>
Edit in Plunker

Output:

Hello World!

Performance Considerations

Improvements with a Directive:

<p data-i18n="common.helloWorld"></p>
Edit in Plunker

Output:

Hello World!

Inline Substitutions

example.json

{
    "nameOuput": "My name is %fullname"
}

Filter:

<p>{{ 'example.nameOutput' | i18n:'Rahul Doshi' }}</p>

Directive:

<p data-i18n="example.nameOutput" data-fullname="Rahul Doshi"></p>
Edit in Plunker

Setting Localized Attributes

Good for when you want to avoid angular's interpolation in setting certain attributes on the DOM element such as placeholder for an input.

<input data-i18n-attr="{placeholder: 'login.emailPrompt'}">
Edit in Plunker

Pluralization

(Experimental -- using inside ng-pluralize)

In your controller:

$scope.numCount = 1;
$scope.pluralStrings = {
    nobodyIsViewing: 'common.nobodyIsViewing',
    onePersonIsViewing: 'common.onePersonIsViewing',
    manyPeopleAreViewing: 'common.manyPeopleAreViewing'
};

On the page:

<input type="number" min="0" data-ng-model="personCount">
<span
    data-ng-pluralize
    data-count="personCount"
    data-when="{
        '0': '{{ pluralStrings.nobodyIsViewing | i18n }}',
        '1': '{{ pluralStrings.onePersonIsViewing | i18n }}',
        'other': '{{ pluralStrings.manyPeopleAreViewing | i18n:personCount }}'
    }">
</span>
Edit in Plunker

Gender

Experimental: using with ng-switch

<div data-ng-switch="user.gender">
<span> data-ng-switch-when="m" data-i18n="common.male"></span>
<span> data-ng-switch-when="f" data-i18n="common.female"></span>
</div>

ng-if also works with filter

Gender:
<span data-ng-if="user.gender == 'm'">{{ 'common.male' | i18n }}</span>
<span data-ng-if="user.gender == 'f'">{{ 'common.female' | i18n }}</span>
Edit in Plunker

grunt-angular-localization

angular-localization

Fin

Slides: http://doshprompt.github.io/angular-localization-lightning-talk

Meetup: http://doshprompt.github.io/angular-localization-meetup