The default blueprint for ember-cli addons.

Internationalization for Ember

The following documentation is for v4.0, which has not yet been released. For documentation on the most recent release, see the v3.1.1 README.

Ember-I18n v4 requires

  • Ember v1.10 - v2.x
  • Ember-CLI
  • jQuery v1.7 - v2.x

For ember-cli projects, run

$ ember install ember-i18n

For non-ember-cli projects, use v3.1.1 or earlier.

Put translation files in app/locales/[locale]/translations.js. Each file should export an object. If the values are Functions, they will be used as-is. If they are Strings, they will first be compiled using util:i18n/compile-translation. A default Handlebars-like implementation is provided. See below for more information on overriding the compiler.

For example,

export default {
  'user.edit.title': 'Edit User',
  'user.followers.title.one': 'One Follower',
  'user.followers.title.other': 'All {{count}} Followers',
  // nested objects work just like dotted keys 
  'button': {
    'add_user': {
      'title': 'Add a user',
      'text': 'Add',
      'disabled': 'Saving...'

The locale generator will generate a new translations file for you:

$ ember generate locale es

Many pieces of ember-i18n rely on the service:i18n. Ask for it wherever you need (likely in Routes and Components):

// app/routes/post.js 
export default Ember.Object.extend({
  i18n: Ember.inject.service(),
  afterModelfunction(post) {
    document.title = this.get('i18n').t('title.post', { post: post });

If you find yourself needing it in many places, you can declare an injection:

// app/instance-initializers/i18n.js 
export default {
  name: 'i18n',
  after: 'ember-i18n',
  initializefunction(app) {
    app.inject('model', 'i18n', 'service:i18n')

If you have a translations API (so you can manage them across apps centrally or so you can deliver only the translations you need), you can add new translations at runtime via the service:i18n:

this.get('i18n').addTranslations('en', {
  'user.profile.gravatar.help': 'Manage your avatar at gravatar.com.'

Set the default locale in config/environment.js:

ENV.i18n = {
  defaultLocale: 'zh'

Override the default by setting locale on the i18n service:

// app/routes/application.js 
export default Ember.Route.extend({
  afterModelfunction(user) {
    this.set('i18n.locale', user.get('locale'));

Note: if you do this in an initializer and use FastBoot, you probably want to use an instance-initializer.

A simple translation:

<h2>{{t "user.edit.title"}}</h2>


<h2>Edit User</h2>

A translation based on a bound key:

<h2>{{t title_i18n_key}}</h2>


<h2>Add a user</h2>

if component.title_i18n_key is 'button.add_user.title'. If it subsequently changes to 'user.edit.title', the HTML will become

<h2>Edit User</h2>

A translation with interpolated values:

<h2>{{t "user.followers.title" count="2"}}</h2>


<h2>All 2 Followers</h2>

Interpolated values can be bound:

<h2>{{t "user.followers.title" count=user.followers.count}}</h2>


<h2>All 2 Followers</h2>

if user.get('followers.count') returns 2.

translationMacro defines a computed property macro that makes it easy to define translated computed properties. For example,

import { translationMacro as t } from "ember-i18n";
export default Ember.Component.extend({
  // A simple translation. 
  title: t("user.edit.title"),
  followersCount: 1,
  // A translation with interpolations. This computed property 
  // depends on `count` and will send `{ count: this.get('followersCount') }` 
  // in to the translation. 
  followersTitle: t("user.followers.title", { count: "followersCount" })

The first argument is the translation key. The second is a hash where the keys are interpolations in the translation and the values are paths to the values relative to this.

The macro relies on this.get('i18n') being the service:i18n. See "i18n Service" docs for more information on where it is available.

If the neither the helper nor the macro works for your use-case, you can use the i18n service directly:

export default Ember.Component.extend({
  // The dependency on i18n.locale is important if you want the 
  // translated value to be recomputed when the user changes their locale. 
  title: Ember.computed('i18n.locale', 'user.isAdmin', function() {
    if (this.get('user.isAdmin')) {
      return this.get('i18n').t('admin.edit.title');
    } else {
      return this.get('i18n').t('user.edit.title');

Ember-i18n includes support for inflection based on a count interpolation. Pluralization rules are based on the Unicode Common Locale Data Repository.

Whenever you pass the count option to the t function, template will be pluralized:

// app/locales/en/translations.js: 
export default {
  'dog': {
    'one': 'a dog',
    'other': '{{count}} dogs'
// Elsewhere: 
i18n.t('dog', { count: 1 }); // a dog 
i18n.t('dog', { count: 2 }); // 2 dogs 

ember-i18n converts 1 to .one and 2 to .other. Depending on the locale, there could be up to 6 plural forms used: 'zero', 'one', 'two', 'few', 'many', 'other'.

If you want to override the inflection rules for a locale, you can define your own in app/locales/[locale]/config.js. For example, to add support for zero to English:

// app/locales/en/config.js: 
export default {
  pluralForm: function englishWithZero(n) {
    if (=== 0) { return 'zero'; }
    if (=== 1) { return 'one'; }
    return 'other';

ember-i18n includes a default compiler that acts mostly like (unbound) Handlebars. It supports interpolations with dots in them. It emits strings that are marked as HTML-safe.

The compiler treats interpolated values as HTML-unsafe by default. You can get HTML-safe interpolations in two ways:

(1) Mark the interpolated value 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)

(2) Use triple-stache notation in the translation:

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

In general, the first method is preferred because it makes it more difficult to accidentally introduce an XSS vulnerability.

The compiler also adds Unicode RTL markers around the output if the locale specifies it. You can override this behavior by setting rtl in the locale's configuration file:

// app/locales/ar/config.js: 
export default {
  rtl: true

You can override the compiler by defining util:i18n/compile-translation. For example, if your translation templates use %{} to indicate an interpolation, you might do

// app/utils/i18n/compile-translation.js 
const interp = /%\{([^\}]+)\}/g;
const escape = Ember.Handlebars.Utils.escapeExpression;
const get = Ember.get;
export default function compile(template) {
  return function(context) {
    return template
      .replace(interp, function(imatch) {
        return escape(get(context, match));

When t is called with a nonexistent key, it returns the result of calling util:i18n/missing-translation with the key and the context as arguments. The default behavior is to return "Missing translation: [key]", but you can customize this by overriding the function. The below example spits out the key along with the values of any arguments that were passed:

// app/utils/i18n/missing-translation: 
export default function(localekeycontext) {
  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 a missing translation is encountered, a missing event is also triggered on the i18n service, with the key and the context as arguments. You can use this to execute other missing-translation behavior such as logging the key somewhere.

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