EmberJS i18n: A beginner’s guide

In this article, you will learn how to implement EmberJS i18n (internationalization) into your application. Internationalization is a part of an application’s development strategy to facilitate normalizing the app for a different region or culture, including the language. In the world of coding, internationalization is often referred to as i18n simply because of the term’s 20-character length.

The underlying purpose of app i18n is to ensure that it remains intuitive and user-friendly across a wide range of countries and regions. For instance, the word “internationalization” itself is spelled differently in various countries. North Americans spell it with a ‘z’, while UK English speakers replace that ‘z’ with an ‘s’. Similarly, i18n is also necessary to make sure apps adapt to a multitude of formulations used in different languages and countries.

In this EmberJS i18n article, we’ll cover the following topics:

  • Setting up an ember-intl module.
  • Translating and internationalizing your application using ember-intl.
  • Maintaining and modifying your translations.
  • Switching languages dynamically.

Getting started

EmberJS is a modern JavaScript framework that is designed to enhance the web app creation experience for professional developers. It reduces small differences between apps, so they are more functional. Most importantly, this is all done by adding a single light layer of code on top of the native JS. It also offers forward and backward compatibility so organizations can always have the latest Ember version.

Prerequisites for integrating EmberJS i18n

In order to fully understand this tutorial, you need to meet the following prerequisites:

  • Working familiarity with core HTML, CSS, and JavaScript.
  • NPM and Node.js on the machine you’re going to use for development.
  • A basic understanding of JS modules, classes, and fundamental ES6 syntax such as template strings, arrow functions, and destructuring.

How to install Ember CLI

Ember CLI provides you with a native method to create and work on EmberJS projects. Before you go any further, you are required to install Ember CLI from NPM by typing the following command:

npm install -g ember-cli

Once you have installed Ember on your development machine, you will have the option to use the ember binary. This can be accessed through your terminal to create, serve, and develop new projects.

Creating a new app with EmberJS

To build a new application with EmberJS i18n support, you first need to create a new directory that will contain the scaffolding for your new app. Before you create a new directory, ensure that you have navigated to somewhere suitable in the terminal prior to writing the code. You can either create it on your desktop or in documents directories so the app can be found easily.

ember new demoapp

Enter the below if you are using Windows:

npx ember-cli new demoapp

This creates a production-ready environment for app development that covers a wide variety of default features. These include the development server, plug-in architecture, the latest JS, automated testing environment, minification, transpilation, and conventions, among others.

Now you are ready to create a new project with the new command. Your command will look like this:

ember new ember-demo-app

You can use any valid name you wish; we have named it ember-demo-app for the purposes of this guide. You can navigate inside your app’s root folder using the cd command:

cd ember-demo-app

Now, you are in a position to utilize a wide variety of Ember commands to work with your app. For instance, you can serve the app using the serve command:

ember serve

After you have reached this point, you can start implementing Ember i18n by adding ember-intl.

EmberJS i18n with ember-intl

Setting up and configuring ember-intl

If you haven’t installed ember-intl, the easiest and quickest way to do it is by using ember-cli, like so:

ember install ember-intl

This will add Format.JS, ember-intl.js, and en-us.yaml files to your app, as well as config and translations directories, respectively. Now you can initiate the server by using the ember serve command. That being said, keep in mind that Ember is quite a particular framework that uses a lint tool to analyze your code style.

This means that as soon as you initiate the command, you will see a message related to the ‘no-bare-strings’ rule. You will need to configure it when using ember-intl. You can prevent this warning by including the following code in the template-lintrc.js:

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

This indicates that you will need to use the {{t 'translation.id'}} helper instead of plain text in your templates. If you do use plain text, please note that it won’t be translated. The helper will give you the option to use the following as valid parameters:

  • true/false – you can enable or disable it.
  • array – includes whitelisted strings.
  • object – contains whitelist, globalAttributes, elementAttributes keys.
  • plain text.

You can convert the template with the following bar helper; however, it won’t give you any translations and will return an error message. This is only because you haven’t added any translation files yet.

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

{{outlet}}

Creating language files and defining translations for EmberJS i18n

You will have to create a language file for the source language of your app and the one you want it to be translated into: your target. To create the language files, you can use the generate locale command along with the abbreviations of the languages you want to add (e.g. ‘de’ for German), as shown below:

ember generate locale en
ember generate locale de

These files or locales contain all translatable strings and are used in your app templates and code. The format of the code is quite easy to follow:

// app/locales/en.js

export default {
  "greeting": "Hello User",
  "task-create": "Create new task",
  "task-delete": "Delete task",
  "task-open": "You have open tasks"
};

To define your translations, you will have to put your intended translation files in app/locales/[locale]/translations.js. Every translation file should export an object. Note that if the values are in the form of functions, they will be executed as they are but if they are in the form of strings, they will need to go through a compilation process using the following:

util:i18n/compile-translation

If you want to use a custom format for your translation, you can check out this guide, which includes overriding the default translation compiler.

Adding your first translation

Once you have gone through all the above steps, you are now ready to add your first translation. In order to do that, you will need to change the application.hbs in the following way:

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

{{outlet}}

Now, if you run the ember serve command again, your application will return an error saying:

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

That’s because you have not included any translations yet. At this point, open translations/en-us.yaml and change it to the following:

hello:
  world: Hello world!

What you see here is that EmberJS relies on a period (.) to define a separation between subentries when you are working in a YAML file. Now you can refresh the page to see the following:

Hello world!

Handling JSON and YAML files

When working with ember-intl, the framework also supports JSON files, but it all comes down to your personal preference. You can choose either of the two. YAML is comparatively compact if you are looking to write the code on your own and it doesn’t have a lot of restrictions. This is especially true in terms of using additional commas in your lists, which returns errors if you are working with JSON files.

For this EmberJS i18n tutorial, we are going to continue to use YAML as the default. Moreover, you might already know that there are a variety of tools available on the net that can convert between YAML and JSON files. There isn’t a wrong format here.

Adding a new language

Since we are already using German for this demo app, we can continue with it. You can use any language or languages you want. In order to do this, use the following script:

ember create translation de-de

You can also create the file as shown above. It is a slightly different method but achieves the same result. If you want to create a translation file, here is the code:

hello:
  world: Hallo Welt!

Switching languages

Once you have learned to add translation files and languages, you need to know how to switch them. When it comes to switching a language in EmberJS i18n, you can do it in two different ways:

  • Specifying the language on app startup
  • Switching languages dynamically or at runtime

Specifying the language on app startup

First, let’s take a look at how you’d set a specific language on app startup. You will need to create the app/routes/application.js including the content mentioned below. This code will switch the language to the German, or de-de, file that you created by adding a new language file.

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']);
  }
});

Now if you run the app, you will see text change to “Hallo Welt!”.

Switching languages dynamically or at runtime

For this to work, you will need to create and add an application controller inside the app/controllers/application.js file.

After you have created the controller, you need to add the following code to enable your app to change the language at runtime:

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 is used to call the template in order to enable a locale (language) change upon button click. This script also delivers various properties to acquire the currently enabled language, a list of accessible languages, and a list that includes the language name and flag. The following template can be used to generate a list of click buttons with the languages that are available.

Add the code mentioned below to app/templates/application.hbs to generate a click button for every language, in this case it is German:

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

If you want to dress up your buttons, you can use your CSS skills in app/styles/app.css to highlight the language that is currently selected. This will give your buttons a more aesthetic feel but it’s not exactly within the scope of this tutorial.

Defining parameters, selections, and pluralization

Since your translations must sound natural and not robotic, you will need further control over them. This being in terms of defining different language parameters and ensuring pluralization occurs accurately. We are not going to discuss all the scripts here, but some of the simple and most used parameters and pluralization rules are as follows.

Defining parameters

You will need to add the following code to application.hbs:

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

Once you have done that, add the following to your language files i.e. de-de.yaml and en-us.yaml:

greeting: 'Hallo {name}!'
greeting: 'Hello {name}!'

The app will now use the provided value in the name parameter to replace {name}. For example:

{{t "greeting" name="John"}}

Defining selections

If you want to change the translation depending on the parameters introduced, you can do so by relying on the ICU syntax included in ember-intl. This particular syntax will enable you to choose various strings and you will need to make changes inside application.hbs.

You will use the following code:

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

If we take the greeting translation example, the syntax will resemble the below script:

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

If you are using select, note that it can take 3 comma-separated parameters including the following:

  • the syntax select
  • the variable name being used for selection
  • a list of probable values along with the text in {}

You also have the option to include extra variables in {} in translation texts, such as the {name} in the provided example. Now you can add the following code to en-us.yaml:

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

de-de.yaml:

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

Pluralization

In EmberJS, you have the capability to modify translation text based on numbers. For instance:

  • No cars
  • One car
  • 2 Cars
  • and so on

To do this, you will need to add the following script in application.hbs:

<p>{{t "cars" itemCount=0}}</p>

<p>{{t "cars" itemCount=1}}</p>

<p>{{t "cars" itemCount=4}}</p>

The translation syntax will look like this:

You have {itemCount, plural,
=0 {no cars}
one {one car}
other {{itemCount} cars}
}.

Three comma-separated values are used in plural syntax. These values include:

  • plural syntax
  • count of items
  • a list of probable values along with the text in {}

The probable value options are listed below:

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

Therefore, you can add the following code to en-us.yaml:

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

and this to de-de.yaml:

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

Compiling your EmberJS i18n translations

The ember-intl framework has a default compiler that plays the role of Handlebars and is compatible with interpolations. It generates strings that have been designated as HTML-safe. If there are any interpolated values, the compiler marks them as HTML-unsafe. If you want HTML-safe interpolations, this can be achieved using the following two methods.

Mark interpolated values as HTML-safe

seeUserMessage: Ember.computed('i18n.locale', 'user.id', function() {
  var userLink = '<a href="/users/' + user.get('id') + '">' + user.get('name') + '</a>';
  return this.get('i18n').t('info.see-other-user', {
    userLink: Ember.String.htmlSafe(userLink)
  });
})

Use triple-stache notation

export default {
  'info.see-other-user': "Please see {{{userLink}}}"
};

Ideally, using the first method is recommended since it makes it more difficult to introduce any XSS vulnerabilities by mistake.

Adding right-to-left language support

If you want to introduce right-to-left or RTL support to your translations, this can be done by introducing Unicode RTL markers around your output translations where required by the language. You have the option to override this by setting rtl in the language configuration file (Arabic in this case):

// app/locales/ar/config.js:

export default {
  rtl: true
}

How to handle missing translations

When you call the t function with a key that does not exist, it returns the following with the context and key as arguments.

util:i18n/missing-message

The default way is to return a “Missing translation: [key]”. However, you can change this via function override. The following script will return the key accompanying the values of the arguments passed:

// app/utils/i18n/missing-message.js:

export default function(locale, key, context) {
  var values = Object.keys(context).map(function(key) { return context[key]; });
  return key + ': ' + (values.join(', '));
}

// Elsewhere:

t('nothing.here', { arg1: 'foo', arg2: 'bar' }); // => "nothing.here: foo, bar"

When there is a missing translation, the i18n service also encounters a missing event with the context and the key. You can utilize these to implement other missing-translation behaviors. For instance, logging your keys somewhere else with this script:

i18n.on('missing', function(locale, key, context) {
  Ember.Logger.warn("Missing translation: " + key);
});

Simplify your life with Lokalise

By now you are probably thinking that supporting multiple languages on a sizeable website is probably going to be a pain. And, honestly, you are right. Luckily, there is a solution to this problem: Lokalise – the platform that makes working with localization files much simpler. Let me guide you through the initial setup, which is not too complex really:

  • To get started, grab your free trial.
  • Install Lokalise CLIv2 which will be used to upload and download translation files.
  • Open your personal profile page, navigate to the “API tokens” section, and generate a read/write token.
  • Create a new project, give it any name, and set English as a base language.
  • On the project page click the “More” button and choose “Settings”. On this page you should see the project ID.
  • Now from the command line simply run lokalise2 file upload --token <token> --project-id <project_id> --lang_iso en --file PATH/TO/PROJECT/translations/en.yml while providing your generated token and project ID (on Windows you may also need to provide the full path to the file). This should upload the English translation to Lokalise.
  • Navigate back to the project overview page. You should see all your translation keys and values there. Of course, it is possible to edit or delete them, as well as add new ones. Here you may also filter the keys and, for example, find any untranslated ones which is really convenient.
  • After you are done editing the translations, download them by running lokalise2 file download --token <token> --project-id <project_id> --format yaml --dest PATH/TO/PROJECT/translations/ Great!

Lokalise has many more features including support for dozens of platforms and formats, the ability to order translations from professionals, and even the possibility to upload screenshots and read text from them. So, stick with Lokalise and make your life easier!

Conclusion

Once you have gone through the above steps, you will be in a much better position to manage the internationalization of your apps using EmberJS. Before you launch the app, ensure that you test it again and again to iron out any bugs and make it as perfect as possible.

Related posts

Sign up to our newsletter

Get the latest articles on all things localization and translation management delivered straight to your inbox.

Read also
Localization made easy. Why wait?
The preferred localization tool of 1500+ leading global companies