Internationalization(i18n) or translations are an integral part of any application that caters to a global audience. Rails supports this out the box.
To add a set of translations to your rails app, it is fairly easy. By default,
rails will provide an en.yml
inside config/locales
folder.
en:
hello: "Hello world"
What this provides is that the key hello
inside locale :en
will have the
value Hello world
.
In this post we are going to focus on how to grow the translation files as the application gets bigger and avoid clutters and duplications as much as we can.
Consider an application with namespaces and a set of different contexts under it. It's common to end up having a structure like below in translation files.
en:
admin:
attendance
policies:
index:
blank:
title: Some title
We have some namespaces to cross until we reach the key we require which is
title
.
Consider the directory app/views/admin/attendance/policies
for all the
views for attendance namespace under admin.
It contains several files index.html.slim
, _table.html.slim
, blank.html.slim
Now, to use that title for the file blank.html.slim
we would be using it as
t('admin.attendance.policies.index.blank.title')
.
This is not good, and this can only get worse if our keywords get bigger
or if we have more namespaces.
Use rails lazy-lookup
Rails provides us with a lazy lookup functionality where in which we don't have to provide the whole path to the translation if it is directly in sync with the path to the view.
Let me refactor the above and show you.
I updated the view file app/views/admin/attendance/policies/_blank.html.slim
to have the translation as: t(.title)
Now I update the translation file as below (please note the hierarchy)
en:
admin:
attendance:
policies:
blank:
title: Some title
Here I removed the index and made the blank
key come directly under the
policies
key. What this resembles is the same directory structure as we
have in our app (check the path shared above).
If this structure matches, rails will automatically pick up t('.title')
.
Long chained paths are not required.
This would require us to re-arrange the translation file a bit but the extra effort would be worth it.
Grouping common terms
We have a lot of forms and components which make use of the same words as
Submit
, Cancel
etc that are being used in many places. We should group them
under minimal hierarchy i.e without a long chain. For example, form related
things can be grouped under
en:
form:
submit: Submit
cancel: Cancel
Then to be used as t('form.submit')
or t(:submit, scope: :form)
FYI: You can also chain scopes like this t(:key, scopes: [:scope_one, :scope_two]),
let's stick to minimal as possible
It may not be form but could be any other entity of the app that you could come
across, end goal being to prevent this
t('admin.some_module.some_resource.some_action.some_form') == "Submit"
and
t('admin.another_module.another_resource.another_action.another_form') == "Submit"
NOTE: I have seen the usage of human_attribute_name
, it's also good in
abstracting out the complexity - wasn't mentioned here because the intent of
this write up was to primarily focus on views and partials that may not be
dealing with model objects all the time, even the ones that are having a model object may not be displaying the exact name.
Splitting up the file
Since we have lots of modules we could go ahead and split up the translation
file as well. Considering the above, any module-specific translations can be
made to go under
locales/admin/<module_name>/*.yml
The current example we are looking at have all the translations at this
location: config/locales/en.yml
.
Following the split-up approach, we will end up with the path like
config/locales/admin/attendance/en.yml
Rails will lazy lookup the translation even in this manner.
Although splitting up would not help reduce redundancy, it would help in better arrangement.
Approach
Combining all three methods would be an ideal approach. Start with making lazy lookup work and then re-arrange the commonly used ones and finally splitting the files up. There is no hard and fast rule as in it should be done in the same order but eventually, we should be able to do all these.
Deciding a word to be moved globally or under a namespace is a preference, example if something like 'Step 1' is used in all modules then it could very well be moved out of the modules. Only the step definitions could change. If a word is only used in two modules or so it's okay to still have it in the modules itself, but the ideal use of translations is to not repeat any words. i.e if we need to change something we have to change it only in one place.
TIP: While doing a refactor take a look at this gem i18n-tasks. It can help you find and manage missing and unused translations.
Reference
Check out all the nifty extras that you can do with the i18n api here Rails doc