Better Tests Through Internationalization

Derek Prior

Internationalization (i18n) is the process of adapting computer software to different languages. In Rails, this means extracting all strings from you views and controllers and placing them (by default) into YAML files that live in config/locales. So why would you want to bother with this if your site is only available in English?

The oft-cited reason for turning to i18n before there’s even a hint of needing to support additional languages is that it’s simply easier to do from the start. This is absolutely true but there are several other benefits to i18n that make it a worthwhile pursuit and something we suggest for all applications. Today we’re going to focus on the effect i18n has on your tests.

i18n Makes Your Tests More Resilient

Consider the very basic feature spec and view below.

spec/features/visitor_signs_in_spec.rb:

feature 'user views dashboard' do
  scenario 'sees welcome message' do
    user = create(:user)
    visit dashboard_path(as: user)

    expect(page).to have_content "Welcome, #{user.name}."
  end
end

spec/views/dashboards/show.html.erb:

Welcome, <%= user.name %>.

One day the client decides that the welcome message is stale and wants to replace it with, “We’re so happy to see you again, #{user.name}.” Just this very simple request in this completely contrived example is going to result in two code changes in two files. Let’s take a look at how this change would have affected our application if we had been using i18n from the start:

spec/features/visitor_signs_in_spec.rb:

feature 'user views dashboard' do
  scenario 'sees welcome message' do
    user = create(:user)
    visit dashboard_path(as: user)

    expect(page).to have_content welcome_message_for(user)
  end

  def welcome_message_for(user)
    t('dashboards.show.welcome', user: user)
  end
end

spec/views/dashboards/show.html.erb:

<%= t('.welcome', user: user) %>

config/locales/en.yml:

dashboards:
  show:
    welcome: "Welcome, %{user.name}"

Now the requested change is localized to a single line in config/locales/en.yml. Our specs no longer depend on copy that can change regularly but on i18n keys which change far less frequently. Following the common Rails convention, the key will only change if the name or path of the view changes.

Tips for Getting Started

Familiarize yourself with the Rails Internationalization API Guide. Rails has great support for i18n throughout. You can review the default locale files to see the keys that power Rails’ validations and helpers.

  • If you use Vim, you’ll likely find the vim-i18n plugin useful when extracting translations from your views and controllers.
  • As you go through the process of extracting translations at some point you are likely to add a key, but not the corresponding translation. Rails will fall back to a reasonable string based on the key, but without manual inspection of the site, you’ll have no way of knowing the key is missing. You can make missing translations fail your tests by adding the following to an initializer:

      if Rails.env.development? || Rails.env.test?
        I18n.exception_handler = lambda do |exception, locale, key, options|
          raise "missing translation: #{key}"
        end
      end
    
  • Put some level of effort into keeping your i18n file organized alphabetically. The gem i18n_yaml_sorter can do this for you automatically. Without any organization it’s easy to introduce namespace collisions which have the unfortunate effect of clobbering pre-existing keys in YAML.

  • An engine such as Tolk or a service like Locale can move the process of updating text away from git and YAML and into a database-backed web application to expose it to more people.

If you’re not using i18n in your app today, the process of getting started can be daunting. Using i18n as a means to improve your specs as you add and refactor features is a good way to slowly internationalize your application while seeing immediate benefit. I heartily recommend i18n for even the smallest of internal-only web applications.

Do you have another interesting use for the Rails i18n framework? Let us know by tweeting @thoughtbot.