Ryan Oglesby

Fun With Rails I18n

January 29, 2013

Skipping the Basics…

Anyone wanting to develop a truly internationalized application needs to address translating the text of the app into the supported languages. Fortunately for you Rails devs out there, the framework provides an easy mechanism for managing and performing those translations, I18n.translate or shortly I18n.t being the forefront. I am just going to mention the basics here and then move right on to some of the more fun features of Rails translations that every Rails ninja needs to know.

So we all know how to do basic translations:

en:
  hello-world: 'Oh hai, World, you are looking nice'
  views:
    welcome: 'Welcome, %{user_name}'
t('hello-world')  # Oh hai, World, you are looking nice
t('views.welcome', user_name: 'OptimusPrime')  # Welcome, OptimusPrime

Now some things you may not know about.

Pluralization

When you do translations you don’t have to try to hack together interpolated strings using ActiveSupport#pluralize. Pluralizations are baked right in.

en:
  views:
    messages:
      zero: 'You got no messages here fool!'
      one: 'Only 1 message right now.'
      other: 'You have %{count} messages.'
t('views.messages', count: 0)  # You got no messages here fool!
t('views.messages', count: 1)  # Only 1 message right now.
t('views.messages', count: 5)  # You have 5 messages.

HTML Safe

As a general rule, I avoid putting HTML markup in a translation string. HTML markup should be in the view where it belongs. However, despite my best efforts, sometimes it is unavoidable - which is okay in certain situations. Furthermore, sometimes the variables passed into a translation will contain HTML markup. The default strategy in this situation that I have seen is to use raw or html_safe. While this does the job, it adds unnecessary method calls when the HTML safe-ification can be handled directly by I18n using the _html suffix.

en:
  views:
    account-will-be-locked_html: 'Your account is about to be <strong>locked</strong>'
    remaining-characters:
      html: 'Remaining characters: %{amount}'
t('views.account-will-be-locked_html')  # 'Your account is about to be <strong>locked</strong>' (marked HTML safe for the view)
t('views.remaining-characters.html', amount: '<em>50</em>')  # 'Remaining characters: <em>50</em>' (marked HTML safe for the view)

Though you do receive the added benefit of avoiding unnecessary html_safe calls, the real winner here it that by being explicit in the locale file, you tell any other developers looking at it which strings are expected to have HTML. Easy win for visibility and communication.

Watch Out for yes/no!

This is a weird one. I am actually not sure why this happens, and would appreciate if anyone can shed some light on the underlying reason for this. If you try to use the key “yes” or “no,” I18n.t will not be able to find it.

en:
  views:
    yes: 'Sure thing'
    no: 'No way!'
t('views.yes')  # <span class="translation_missing" title="translation missing: en.views.yes">Yes</span>
t('views.no')  # <span class="translation_missing" title="translation missing: en.view.yes">No</span>

Only solution I know is just to not use “yes” or “no” as keys, which is not ideal if you are adding a translation for those exact words.

Literal Naming FTW

It can be quite tempting to use semantic keys in your locale YML files. I see things like “title,” “introduction-1,” or “header-text.” While this will work fine, I prefer to use literal keys that reflect the actual content of the translation (in the default locale) so my views are easier to read for myself and other developers.

- # Hard to read. I have to jump out of my code just to read my own code! Ergggggghhhh
.header
  = t('views.header.user-intro', user: @current_user)

.main-content
  %p
    = t('views.common.site-description-1')
  %p
    = t('views.common.site-description-2')

  %ul
    %li
      = t('views.common.user-perks-1')
- # Easy to read. I understand my own code. Yessssssssss
.header
  = t('views.header.welcome-to-the-site', user: @current_user)

.main-content
  %p
    = t('views.common.we-have-doo-dads')
  %p
    = t('views.common.and-foo-bar-widgets')

  %ul
    %li
      = t('views.common.receive-service-any-time')

That’s all for now! Most of this plus everything else you wanted to know about i18n can be found in the Rails Guides.

Bye! Adios! Adieu! Aloha!

Blog comments powered by Disqus.