home

3 Improvements for a Rails I18n Workflow

written by on · Comments

Localizing a web application can be a real pain, especially when you don’t use the right tools. I18n in Rails works great and offers a ton of flexibility to optimize your workflow. A lot of tools focus on the translation process for the language files but overlook the development workflow. At Moneybird we have developed our own I18n workflow to make it faster to work with translations in development.

1. Using an I18n exception handler

We defined our own I18n exception handler to handle missing translations. Each missing translation is added to config/missing_translations.yml. When an untranslated key is used in the source code, the key is automatically added to this YAML file, including the right scope.

1
2
3
4
5
6
7
8
if Rails.env.production?
  I18n.exception_handler = I18n::Workflow::ExceptionHandler.new
end

I18n.t(:foobar)
I18n.t(:foobar, scope: [:first, :second]
# Automatic Rails view scoping
t(".foobar")

This results in the following missing translations YAML.

1
2
3
4
5
6
7
8
9
---
en:
  first:
    second:
      foobar: ""
  foobar: ""
  some_controllers:
    action:
      foobar: ""

This file always contains a list of keys you need to translate. After translating the keys in config/missing_translations.yml, a simple Ruby script merges the translations with the main locale file in config/locales.

1
bundle exec merge_missing_translations

Advantages: Easy overview of keys to translate, scoping is always optimal and we can change it whenever necessary. The main locale files are always nicely sorted.

2. Cascading

In previous workflows we always stored many keys with the same translation. Moneybird is centered around invoices, so the key invoice: Invoice would exist multiple times in a translation file. I18n has a great feature called cascade which allows the lookup to cascade to higher levels in the YAML file.

1
2
3
4
5
6
en:
  invoice: Invoice
  download: Download
  invoices:
    show:
      download: Download PDF

To activate cascading, you need to include it into the backend:

1
2
3
4
5
6
7
8
9
10
I18n.backend.class.send(:include, I18n::Backend::Cascade)

# Inside view invoices/show.html.erb
t(".invoice", cascade: true)   # => "Invoice"
t(".download", cascade: true)  # => "Download PDF"
t("download", cascade: true)   # => "Download"

# To prevent typing cascade: true, we created
# a backend extension which makes cascade true by default.
I18n.backend.class.send(:include, I18n::Workflow::AlwaysCascade)

Using the Rails translation helper we get good controller and action scoping for free. Specific translations can be included in a lower level of the YAML file, while global translations are included in the higher levels and no longer translated multiple times.

Advantages: Reduced number of duplicate keys, therefore less keys to maintain and to translate in a later stadium.

3. Scoping

Using cascading by default can cause conflicts between keys and scopes. When calling I18n.t("invoices") using the previously mentioned YAML file, we get a Hash with all translations in the invoices scope. We probably want only the translations for invoices.

These issues are resolved by explicitly appending _scope to each scope in I18n. Inside our projects, the previous YAML file would look like:

1
2
3
4
5
6
7
en:
  invoice: Invoice
  download: Download
  invoices: Invoices
  invoices_scope:
    show_scope:
      download: Download PDF

By modifying the backend we were able to extend the behaviour of I18n to always append _scope to a requested scope.

1
I18n.backend.class.send(:include, I18n::Workflow::ExplicitScopeKey)

The exception handler also appends the full scope name to config/missing_translations.yml, so we hardly have to think about appending it during development.

Advantages: No conflicts between keys and scope, making cascading easier and less error-prone.

(Small) disadvantage: Some Rails helpers expect I18n to return a hash with options that can be used in the helper. For some specific cases the _scope should therefore be omitted.

4. Bonus: Continuous Integration

Our workflow has a really nice bonus: automation during CI. When a developer creates a new UI and uses translations that are not present in the locale file, these keys should be added to make the UI complete. When config/missing_translations.yml is filled after running our test suite, the tests fail and the developer is notified. This mechanism prevents untranslated keys from being present in our final product.

Conclusion

For the development of Moneybird our new I18n workflow proved to be valuable. Due to the great architecture of I18n we were able to improve our workflow without any monkey patching.

We have open sourced different parts of our I18n workflow. GitHub repo: https://github.com/moneybird/i18n-workflow

comments powered by Disqus