How to translate your Ember.js application with ember-intl

How to translate your Ember.js application with ember-intl

Overview

In this tutorial you are going to learn:

  • How to translate your Ember.js application
  • How to set up ember-intl
  • How to edit and maintain your translations in YAML or JSON files

The source code for the example project is available on github.

Create a simple demo project

Let's start this tutorial with a simple demo application. You can of course skip this section if you already have an application that you want to translate.

Get ember-cli if you don't have it installed yet

npm install -g ember-cli

You should now have ember-cli in your command line. Create an empty new project:

ember new ember-intl-example
cd ember-intl-example
ember serve

You should see ember's default starting screen when you visit http://localhost:4200

Install ember-intl

The easiest way to install ember-intl is using ember-cli:

ember install ember-intl

This adds the following files:

  • create app/formats.js

  • create config/ember-intl.js

  • create translations/en-us.yaml

Warning during ember-serve

Ember is a highly opinionated framework that checks code style using a lint tool. As soon as you start ember serve you'll see the following message:

The no-bare-strings rule must be configured when using a localization framework (ember-intl). To prevent this warning, add the following to your .template-lintrc.js:

rules: {
    'no-bare-strings': true
}

That this means is:

  • You should not use plain text in your templates (because these won't be translated)
  • You have to use the {{t 'translation.id'}} helper instead
  • Lint wants you to configure lint accordingly.

You have the following options as values for the parameter:

  • true/false - enabled/disabled
  • array - an array of whitelisted strings
  • object - an object with the following keys:
    • whitelist- An array of whitelisted strings
    • globalAttributes - An array of attributes to check on every element.
    • elementAttributes - An object whose keys are tag names and value is an array of attributes to check for that tag name.

When the config value of true is used the following configuration is used:

  • whitelist - (),.&+-=*/#%!?:[]{}
  • globalAttributes - title, aria-label, aria-placeholder, aria-roledescription, aria-valuetext
  • elementAttributes - { img: ['alt'], input: ['placeholder'] }

This seems to be a good starting point. Let's use it for the demo... Please update .template-lintrc.js:

'use strict';

module.exports = {
  extends: 'recommended',

    rules: {
    'no-bare-strings': true
  }
};

Adding your first translation

Change the application.hbs to the following:

<h1>{{t "hello.world"}}</h1>	

{{outlet}}

Run ember serve again, and the application now displays an error:

Missing translation "hello.world" for locale "en-us"

That's right... we've not yet added any translations. Open translations/en-us.yaml and change it to:

hello:
  world: Hello world!

What you see here is that ember uses a . to separate sub-entries in the YAML file. The key word is indented.

Refresh and you'll see the Hello world! text.

YAML or JSON?

ember-intl can also work with JSON files — that's simply a matter of personal taste which format you prefer. Let's stick with YAML for this demo because it's the default.

YAML is a bit more compact if you want to write it manually and might be a bit less strict — especially when it comes to additional commas at the end of lists which causes JSON to report errors.

There are loads of tools on the internet that can convert between JSON and YAML — so there's nothing to regret if you chose the "wrong" format.

Adding a new language

Let's now add a translation... we'll use German in this demo — but feel free to use your own native language.

You can use ember create translation de-de for this... or simply create the file — the effect is the same.

Create a new translation file: translations/de-de.yaml.

hello:
  world: Hallo Welt!

Switching languages

Setting the language on startup

You might not see any change in the browser... let's switch the language to de-de:

Create app/routes/application.js with the following content. The code switches to the German (de-de) language file created in the section above.

import { inject as service } from '@ember/service';
import Route from '@ember/routing/route';

export default Route.extend({
  intl: service(),
  beforeModel() {
    this.get('intl').setLocale(['de-de']);
  }
});

Refresh the app and see the text change to

Hallo Welt!

Switching languages at runtime

Create an application controller in app/controllers/application.js with the following content:

import config from '../config/environment';
import Controller from '@ember/controller';
import { computed, get } from '@ember/object';
import { inject as service } from '@ember/service';
import { lookupByFactoryType } from 'ember-intl/hydrate';

const { modulePrefix } = config;

export default Controller.extend({
  intl: service(),
  activeLocale: computed.readOnly('intl.locale'),

  locales: computed(function() {
    return lookupByFactoryType('translations', modulePrefix).map(moduleName => moduleName.split('/').pop());
  }).readOnly(),

  selections: computed('locales.[]', 'activeLocale', function() {
    let active = get(this, 'activeLocale');

    return get(this, 'locales').map(locale => {
      return {
        locale: locale,
        active: active.indexOf(locale) > -1
      };
    });
  }).readOnly(),

  actions: {
    changeLocale(locale) {
      return get(this, 'intl').set('locale', locale);
    }
  }
});

The changeLocale action is called from the following template to activate a locale on button click.

This code also provides some computed properties to retrieve the currently active locale, a list of available locales and a list that contains the locale name and an active flag. The following template uses the code to render a list of buttons with the available languages.

Add the following code to app/templates/application.hbs to render a button for each language:

<div>
  {{#each selections as |model|}}
    <button class={{if model.active "active"}} {{action "changeLocale" model.locale}}>
      {{model.locale}}
    </button>
  {{/each}}
</div>

Add some css in app/styles/app.css to highlight the currently selected language and give the buttons a nicer look:

button
{
	padding: 0.25rem 0.5rem;
	border: 1px solid blue;
	color: black;
	background: white;
	border-radius: 0.25rem;
}

button.active
{
	background-color: blue;
	color: white;
}

You should now see the text from before and 2 buttons that you can use to toggle betwen German and English.

Parameters, pluralization and selections

Simple parameters

app/templates/application.hbs

<h1>{{t "greeting" name="John"}}</h1>

Also add the following lines to translations/de-de.yaml

greeting: 'Hallo {name}!'

and translations/en-us.yaml

greeting: 'Hello {name}!'

The translation now replaces the {name} with the value provided in the name parameter in the template: {{t "greeting" name="Andreas"}}

Selections

You can also change the translation based on parameters using the ICU syntax used by ember-intl.

The syntax allows you to select different strings:

app/templates/application.hbs

<p>{{t "download" name="BabelEdit" type="full"}}</p>
<p>{{t "download" name="BabelEdit" type="trial"}}</p>

The syntax for the greeting translation now looks like this:

Download {type, select,
    pro { {name} (full version) }
    trial { the free trial of {name} }
    other { {name} }
} from our web page!

The select syntax takes 3 comma separated values:

  • the name of the variable that is used for the selection
  • the keyword select
  • a list of possible values followed by the text in {}

You can also provide additional variables in {} in the translation texts, like the {name} in this example.

Add the following to translations/en-us.yaml

download: |-
  Download {type, select,
      pro { {name} (full version) }
      trial { the free trial of {name} }
      other { {name} }
  } from our web page!

and translations/de-de.yaml

download: |-
  Laden sie  {type, select,
      pro { {name} Professional}
      trial {die Test-Version von {name}}
      other { {name} }
  } von unserer Webseite!

Pluralization

You can also change the translation text depending on numbers. E.g.

  • no flowers
  • one flower
  • 2 flowers
  • ...

app/templates/application.hbs

<p>{{t "flowers" itemCount=0}}</p>
<p>{{t "flowers" itemCount=1}}</p>
<p>{{t "flowers" itemCount=4}}</p>

The syntax for the translation is

You have {itemCount, plural,
    =0 {no flowers}
    one {one flower}
    other {{itemCount} flowers}
}.

The plural syntax takes 3 comma separated values:

  • the count of items
  • the keyword plural
  • a list of possible values followed by the text in {}

Options for possible values are:

  • zero
  • one, two
  • few, many
  • other
  • =value

Add this to translations/en-us.yaml

flowers: |-
  You have {itemCount, plural,
    =0 {no flowers}
    one {one flower}
    other {{itemCount} flowers}
  }.

and translations/de-de.yaml

flowers: |-
  Du hast {itemCount, plural,
    =0 {keine Blumen}
    one {eine Blume}
    other {{itemCount} Blumen}
  }.

Simplifying your workflow with BabelEdit

Working with a few translations and 2 languages is easy — but things become more complicated when your project grows.

  • Which texts are already translated?
  • Did I add the new translation key to all languages?
  • Did I rename the key in all languages?
  • Which texts are draft, which are final?
  • ...

It's also hard to be consistent in the translation if you have to switch between multiple YAML files all the time... This is where BabelEdit comes to your rescue!

Getting started with BabelEdit

First of all download BabelEdit free trial from here:

Simply drag & drop the whole ember-intl-example folder onto BabelEdit's main window.

Selecting YAML files to edit for ember-intl

BabelEdit detects the ember-intl project and asks you about the file format you use. Click on YAML.

Selecting Languages to edit

BabelEdit now asks you to set up the languages you want use — it should be ok to simply click Ok.

A short overview

The left side of BabelEdit contains a tree view with the translation IDs you use in your YAML files. BabelEdit displays all YAML (or JSON) files in the same view.

Changes like adding, removing or renaming IDs are performed on all files at the same time.

The center view displays the translations selected on the left side.

A spell checker is activated for the translation edit fields. It of course checks each translation in its language. The license of the German spell checking dictionary does not allow us to bundle the files directly with BabelEdit. You have to download and install them following these instructions.

The machine translation features of BabelEdit display suggestions for the currently edited language. It is also possible to automatically translate all texts to preview your application in a new language. You might however have to work on messages containing ICU syntax...

Use the approved flag to mark translations that are final and don't require addtional work.

Add comments to give a translator detailed information about the text. E.g. where it's located, or what parameters to expect.

Use the filter to search for translation ids, words in the text. You can also filter translations by not approved or empty/untranslated.

Export and Import allow you to exchange translations with external translation agencies who neither want to use BabelEdit nor work with YAML or JSON files.

Edit translations for ember.js using ember-intl