Internationalisation (i18n) and Localisation (l10n) for Ionic Apps

In a previous post, I wrote about how Vardyger's Admin UI uses the Angular UI router for handling routes, defining states and sharing data between views. In this post, we'll update the Admin UI in order to add support for internationalisation (i18n) and localisation (l10n).

Note: I've updated Vardyger's project structure to include a directory for locales:

├── /app
    └── /bower_components
        └── /ionic
        ...
    └── /scripts
        └── /controllers
            ├── editor-controller.js
            ├── main-controller.js
            ├── preview-controller.js
            ├── side-menu-controller.js
        └── /directives
        └── /locales
        └── /services
            ├── posts-service.js
        ├── app.js
    └── /styles
        ├── main.scss
    └── /templates
        ├── editor-template.html
        ├── main-template.html
        ├── preview-template.html
        ├── side-menu-template.html
    ├── index.html

...

Install angular-translate

angular-translate is an AngularJS module that makes it easy to add support for internationalisation, localisation, lazy loading and pluralisation to your Ionic apps.

To install angular-translate, enter the following command:

bower install --save angular-translate

Note: Bower will add the module as a dependency in the project's bower.json and install angular-translate in the app/bower_components directory (as per the Admin UI's .bowerrc).

We also want to take advantage of angular-translate's static file loader, so we need to install it too:

bower install --save angular-translate-loader-static-files

Update index.html

Now, we need to update index.html so that it includes the angular-translate and the angular-translate-loader-static-files scripts:

...

<!-- bower:js -->
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-animate/angular-animate.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
<script src="bower_components/ionic/release/js/ionic.js"></script>
<script src="bower_components/ionic/release/js/ionic-angular.js"></script>
<script src="bower_components/angular-translate/angular-translate.js"></script>
<script src="bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js"></script>
<!-- endbower -->

...

Update app.js

First, we need to inject the angular-translate module into our app:

...

angular.module('vardyger', [
  'ionic',                  // inject the Ionic framework
  'pascalprecht.translate'  // inject the angular-translate module
])
  .config(function($ionicConfigProvider, $stateProvider,
    $urlRouterProvider, $translateProvider) {

    $translateProvider
      .useStaticFilesLoader({
        prefix: 'scripts/locales/',
        suffix: '.json'
      })
      .registerAvailableLanguageKeys(['en', 'de'], {
        'en' : 'en', 'en_GB': 'en', 'en_US': 'en',
        'de' : 'de', 'de_DE': 'de', 'de_CH': 'de'
      })
      .preferredLanguage('de')
      .fallbackLanguage('de')
      .determinePreferredLanguage()
      .useSanitizeValueStrategy('escapeParameters');

      ...

Then, we need to configure the static files loader by providing a file prefix ('scripts/locales/') and a file suffix ('.json'). We also need to register our language keys, set a preferred language and a fallback language, determine the user's preferred language and choose a sanitization strategy.

Note: So far I have only defined translations (see below) for English (en) and German (de) and mapped them to locales in registerAvailableLanguageKeys(). For example, if determinePreferredLanguage() returns 'en_US' or 'en_GB' then the static files loader will load 'scripts/locales/en.json'.

Create some translations

Now, we need to create some translations, locales/en.json:

{
  "MAIN_TEMPLATE_TITLE":         "Content",
  "PREVIEW_TEMPLATE_TITLE":      "Preview",
  "EDITOR_TEMPLATE_TITLE":       "Editor",

  "SIDE_MENU_TEMPLATE_CONTENT":  "Content",
  "SIDE_MENU_TEMPLATE_NEW_POST": "New Post",
  "SIDE_MENU_TEMPLATE_SETTINGS": "Settings",

  "ALL_POSTS":                   "ALL POSTS",
  "NO_POSTS":                    "No posts :(",
  "EDIT":                        "EDIT",
  "MARKDOWN":                    "MARKDOWN",
  "PREVIEW":                     "PREVIEW",
  "UPDATE_POST":                 "UPDATE POST"
}

And, locales/de.json:

{
  "MAIN_TEMPLATE_TITLE":         "Inhalt",
  "PREVIEW_TEMPLATE_TITLE":      "Vorschau",
  "EDITOR_TEMPLATE_TITLE":       "Editor",

  "SIDE_MENU_TEMPLATE_CONTENT":  "Inhalt",
  "SIDE_MENU_TEMPLATE_NEW_POST": "neuer Beitrag",
  "SIDE_MENU_TEMPLATE_SETTINGS": "Einstellungen",

  "ALL_POSTS":                   "Alle Beiträge",
  "NO_POSTS":                    "keine Einträge :(",
  "EDIT":                        "BEARBEITEN",
  "MARKDOWN":                    "MARKDOWN",
  "PREVIEW":                     "VORSCHAU",
  "UPDATE_POST":                 "UPDATE BEITRAG"
}

Update the view templates

We also need to update the project's view templates, for example templates/main-template.html:

...

<div class="bar bar-subheader bar-stable">
  <div class="title title-left padding-left">
    <small translate="ALL_POSTS">
    </small>
  </div>

  ...

</div>

...

Note: Where possible you should prefer the translateDirective (e.g., translate="ALL_POSTS") to the translateFilter (e.g., 'ALL_POSTS' | translate).

Switch translations

It is also very easy to switch languages, for example templates/side-menu-template.html:

...

<ion-item menu-close class="item-icon-left item-dark" 
  ng-click="switchLanguage('de_CH')">
  <icon ios="ion-ios-gear" android="ion-settings" default="ion-settings">
  </icon>
  {{ 'SIDE_MENU_TEMPLATE_SETTINGS' | translate }}
</ion-item>

....

The SideMenuController (scripts/controllers/side-menu-controller.js):

...

$scope.switchLanguage = function(key) {
  $translate.use(key);
};

...

The side menu in English:

The side menu in German:

References: