Rails I18n: 3 Quick Tips and 1 Crazy Abuse

Rails’ i18n library is more powerful than it looks. You don’t need to only use it for translation. The i18n library is useful anywhere you want to decouple the text you display from the place you display it.

While I’ve been at Avvo, we’ve used i18n to do some really cool stuff. I’m going to share a few of the things that we learned that have come in handy, along with a crazy abuse of the library that ended up working out incredibly well for us.

Automatic HTML escaping

Do you have lines that look like:

<%= raw(t('form.required_field_header')) %>

in your views? You don’t need that raw, because if your translation key ends with _html, you get escaping for free:

<%= t('form.required_field_header_html') %>

Simple, and more consistent with your other t() calls.

Accessing locales more conveniently

When you build your locale files, you’ll probably have some of your keys grouped under the same parent:

en:
  bugs:
    index:
      new_label: "File a new bug"
      edit_label: "edit"
      delete_label: "delete"

To reference a bunch of these all together, you could reference them by their full key:

<%= t('bugs.index.edit_label') %> | <%= t('bugs.index.delete_label') %>

That’s pretty annoying and repetitive. But t() helpfully takes a scope, so you can reference keys more conveniently:

<% bugs_scope = 'bugs.index' -%>
<%= t('edit_label', scope: bugs_scope) %> | <%= t('delete_label', scope: bugs_scope) %>

If you name your partials well, you don’t even need to specify the scope:

app/views/bugs/index.html.erb
<%= t('.edit_label') %> | <%= t('.delete_label') %>

That is, .edit_label references bugs.index.edit_label, because you’re in bugs/index.html.erb.

ActiveRecord backends

Sometimes, static translations in a yaml file just won’t work for your project. If you use the ActiveRecord i18n backend instead:

config/initializers/locale.rb
require 'i18n/backend/active_record'
I18n.backend = I18n::Backend::ActiveRecord.new

you can look up translations in a translations table in your database. This way, you don’t have to define all your translations up-front. Instead, you can add new translations on the fly.

Setting up the translations table requires a specific migration. The README on the i18n-active_record repo has all the information you need to get it working.

Sharing partials between object types

At Avvo, we have a lawyer directory, attorney reviews, and legal advice. But a few years ago, we didn’t just have lawyers on our site. We had doctors and dentists, too!

A lot of our UI was the same between these professions. But enough was different (for example, what lawyers call “practice areas”, doctors call “specialties”), that it would be hard to share the same views or partials without some pretty ugly code.

Eventually, we thought about trying to lean on the i18n system for this. After all, English lawyerese is kind of a dialect of English, and doctorese is the same way. We didn’t want to block ourselves from eventually using other languages, so we decided to create two new locales: en_jd and en_md.

This turned out to be easy to set up as a custom i18n backend:

class AvvoI18nStore < I18n::Backend::Simple
  def translate(locale, key, options = {})
    begin
      default = options.delete(:default)
      super(locale, key, options)
    rescue I18n::MissingTranslationData => e
      # fall back to "en" if we can't find anything under "en_jd", etc.
      fallback_locale = locale.to_s.split("_").first
      super(fallback_locale, key, options.merge(:default => default))
    end
  end
end

I18n.backend = AvvoI18nStore.new

And defining translations was just as easy:

en_jd:
  practice_area: "practice area"

en_md:
  practice_area: "specialty"

It worked great for translating entire partials, too (note the filename):

app/views/questions/_answer_badge.en_jd.html.erb
<!-- Lawyer badge HTML -->
app/views/questions/_answer_badge.en_md.html.erb
<!-- Doctor badge HTML -->

And it even fell back to the en locale, for shared translations:

en:
  leaderboard:
    title: "Leaderboard"

When you were in the “Doctors” section of the site, we changed the default locale to :en_md, and vice versa:

I18n.locale = :en_md

And everything just worked!

I’m not sure I’d recommend this. It’s a little nuts, thinking of different professions having different languages. But it worked amazingly well while we did it. And it reveals just how much power simple tools like i18n can have.

Where do you pick up this stuff?

Last week, I talked about taking deep dives into specific technologies as a way to move from a beginner to an expert. This is another example of that: most of what we learned about i18n we learned from reading the Rails guides and API docs.

So, take that extra step. Learn the tools you rely on well. You’ll find that you’ll run into conveniences and tricks that will not only make you more productive, but might unlock brand new categories of solutions you’d never even have thought of otherwise.

Pushing through tutorials, and still not learning anything?

Have you slogged through the same guide three times and still don't know how to build a real app?

In this free 7-day Rails course, you'll learn specific steps to start your own Rails apps — without giving up, and without being overwhelmed.

You'll also discover the fastest way to learn new Rails features with your 32-page sample of Practicing Rails: Learn Rails Without Being Overwhelmed.

Sign up below to get started:

Powered by ConvertKit

Did you like this article? You should read these:

Comments