I18n Dynamic Ruby Translations with Lambdas
YAML
files are the number one place to define translations with I18n
. But, did you know that ruby (rb)
files can be another option? Even a more powerful one…
I found out about this while working on a project that uses it, and when I tried to find more about it online I realized it was not a straightforward thing to search for. I hope that with this post, developers can find this more easily and leverage it sooner in their codebase if needed. Please note that this post is based on i18n v1.6.0
–I didn’t try earlier versions.
How to store your translations
As stated in the Rails Guides, translations can be stored in both ruby
and YAML
file formats. Please note that this is also true for any ruby
application using i18n
. Just make sure all the translation files are part of the I18n.load_path
.
# config/locales/en.rb{
en: {
foo: 'bar'
}
}
Which is equivalent to:
# config/locales/en.ymlen:
foo: 'bar'
If both translation files are loaded, the union of the two would make up the set of translations our application will use.
These are still static translation examples we are talking about. Let’s take a look at a more complex scenario next.
Add logic to translations
We know we can pass variables into translations, obviously supported by translations stored in YAML
files. But, what if we need something more complex that cannot be handled by interpolation? Then, the only option left is to embed ruby
logic into our translations. This is where ruby
translations come in handy.
Translations stored in ruby
files support the use of lambdas
. Documentation about this can be found in the code of i18n
itself.
# config/locales/en.rb{
en: {
salutation: lambda do |key, options|
if options[:formal]
"Welcome #{options[:name]}."
else
"Hi #{options[:name]}!"
end
end
}
}
Actually, the string returned by lambda will go through string interpolation too.
# config/locales/en.rb{
en: {
salutation: lambda do |key, options|
if options[:formal]
"Welcome %{name}."
else
"Hi %{name}!"
end
end
}
}
Both of the examples above would behave in the following way:
I18n.t(:salutation, formal: true, name: 'Foo') => 'Welcome Foo.'
I18n.t(:salutation, formal: false, name: 'Foo') => 'Hi Foo!'
Powerful! Isn’t it? A lot of complex translations can be solved with this technique. A quick glance at the use of lambdas
for pluralization
in Rails
can be found in the Rails Guides as well.
A small caveat that the i18n
gem recommends taking into account:
It is recommended to use/implement lambdas in an “idempotent” way. E.g. when a cache layer is put in front of I18n.translate it will generate a cache key from the argument values passed to #translate. Therefor your lambdas should always return the same translations/values per unique combination of argument values.
Conclusion
Both YAML
and ruby
translation files are great to use on a project. While ruby
files are more powerful, YAML
files are more dry and maintainable.
I’ve seen them work together perfectly. They can even team up and interact with each other, letting developers unleash their full potential.
Last, but not least, check out the following example!
# config/locales/en.rb{
en: {
salutation: lambda do |key, options|
if options[:formal]
I18n.t('salutations.formal', name: options[:name])
else
I18n.t('salutations.informal', name: options[:name])
end
end
}
}# config/locales/en.ymlen:
salutations:
formal: 'Welcome %{name}.'
informal: 'Hi %{name}!'