3 Improvements for a Rails I18n Workflow
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.
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.
--- 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
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.
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.
en: invoice: Invoice download: Download invoices: show: download: Download PDF
To activate cascading, you need to include it into the backend:
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.
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:
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.
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.
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