Ruby on Rails provides a well-documented and established Internationalization API for server-side localization. However, many projects I've seen forget to internationalize their front-end JavaScript framework and end up hard-coding text everywhere. For some people, this works, but for larger projects that require localization this becomes unsustainable quickly. Luckily, there's a straight-forward to localize your JavaScript front-end and it can be done by using the i18n-js gem.

There are a couple ways to use I18n-js, for this walk-through we are going to use the Rails AssetPipeline (for better or worse) and the I18n::JS::Middleware approach. Alternatively, you can use the provided rake tasks to build your locales on deploy or in some other process.

How it Works

I18n-js provides middleware that auto-generates localized JavaScript files into the public directory based on your configuration in config/i18n-js.yml. These auto-generated files contain localized JavaScript Objects, which are loaded into the global JavaScript scope and made available to the I18n.js object. Using the middleware, you'll end up with a folder structure within public/i18n that looks something like this (assuming you support all these languages):

public/javascripts
├── i18n
│   ├── en.js
│   ├── es.js
│   ├── fr.js
│   ├── it.js
│   └── jp.js
└── i18n.js

We'll configure our app so that any locales in the config/locales/* directory nested under the top level js: key will be included in these complied JavaScript files. This allows you do to cool stuff, like this:

// Using ES6 String interpolation 

function displayErrorMessage(scope, kind) { 
  var text = 18n.t("js.errors.${scope}.${kind}");

  return new AjaxFlashMessage("error", text);
}

Implementation

First, update your Gemfile.rb and run bundle install.

# Gemfile.rb

gem "i18n-js"

Add this line to your .gitignore as I18n-js will manage the auto-generated JS files.

# .gitignore

# Generated by i18n-js
public/javascripts/i18n.js

Add the middleware:

# config/application.rb

module MyApp

  class Application < Rails::Application

    # It's a good idea to set a default
    config.i18n.default_locale = :en
    # If you don't specify these, I18n::JS will generate files for all locales
    config.i18n.available_locales = %w(en es fr it)
    config.middleware.use I18n::JS::Middleware
  end
end

Add the configuration yml file:

# config/i18n-js.yml

translations:
- file: "public/javascripts/i18n/%{locale}.js"
  only: '*.js.*'

Update your JavaScript manifest file. Unless this will help you with page rendering, I recommend you load this after page-load with some deferred inclusion process but for simplicity we'll just assume application.js.

# app/assets/javascripts/application.js

//= require i18n/translations

Now we need to tell I18n.js what locale it should be using. We can do this by passing some information to the client-side through a view template.

<%# app/views/layouts/_footer.html.erb %>

<script type="text/javascript">
  I18n.defaultLocale = "<%= I18n.default_locale %>"
  I18n.locale = "<%= I18n.locale || I18n.default_locale %>"
</script>

Finally, let's add our first JavaScript locales. Make sure you nest these under the js: key, or however you have it configured in config/i18n-js.yml.

# config/locales/en.js.yml

en:
  js:
    browser:
      not_supported: |
        This browser is ancient. Please use the latest version of Chrome, Firefox or Safari. 

Restart your server.

Now open up a developer browser console and make sure you have access to the I18n object:

You'll notice there are a lot of various options and functions available to us. We're only going to address the primary use case of localizing some text using the t() function:

> I18n.t("js.browser.not_supported")
> "This browser is ancient. Please use the latest version of Chrome, Firefox or Safari."

Pretty sweet. Now you can go about localizing just about anything. I18n-js mimics the Rails internationalization API and allows for many features, including:

  • Fallbacks
  • Defaults I18n.t("errors.server", { default: "Whoops!" });
  • Interpolation I18n.t("greeting.personal", { name: "John" });
  • Pluralization I18n.t("alert.count", { count: 5 });
  • Scoping I18n.t("alert.count", { scope: "dashboard" });

Take a look at the docs for all the features available to you https://github.com/fnando/i18n-js.