Rails I18n – Seven Best Practices That You Should Know About
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
<strong>Rails</strong> Internationalization (<strong>I18n</strong>)<br />
<strong>Seven</strong> <strong>Best</strong> <strong>Practices</strong> <strong>That</strong> <strong>You</strong><br />
<strong>Should</strong> <strong>Know</strong> <strong>About</strong><br />
phraseapp.com | sales@phraseapp.com | +49-40-357-187-76 | twitter.com/phraseapp | facebook.com/phraseapp | linkedin.com/company/phraseapp
<strong>Rails</strong> Internationalization (<strong>I18n</strong>) - <strong>Seven</strong> <strong>Best</strong> <strong>Practices</strong> <strong>That</strong> <strong>You</strong> <strong>Should</strong> <strong>Know</strong> <strong>About</strong><br />
Table of Content<br />
Split Translations 4<br />
Take Advantage Of Nesting 5<br />
Employ “Lazy” Lookups 6<br />
Enforce Available Locales 7<br />
Utilize Localized Views 8<br />
Take Advantage Of Variables 8<br />
Helper Methods 10<br />
Stick With PhraseApp! 10<br />
Conclusion 11<br />
phraseapp.com | sales@phraseapp.com | +49-40-357-187-76 | twitter.com/phraseapp | facebook.com/phraseapp | linkedin.com/company/phraseapp
<strong>Rails</strong> Internationalization (<strong>I18n</strong>) - <strong>Seven</strong> <strong>Best</strong> <strong>Practices</strong> <strong>That</strong> <strong>You</strong> <strong>Should</strong> <strong>Know</strong> <strong>About</strong><br />
Are you creating web applications with the Ruby on <strong>Rails</strong> framework? If you are, have a look at<br />
these seven <strong>I18n</strong> best practices, including advices that will help you organize your translations<br />
better and take full advantage of <strong>Rails</strong> power.<br />
I really love crafting web applications with the Ruby on <strong>Rails</strong> framework. Creating small<br />
maintainable applications is not a problem at all <strong>–</strong> your code is beautifully structured and<br />
everything fits in. However, as your app grows, it may become significantly harder to organize<br />
parts of the code so that it does not turn into something monstrous and unmanageable. This, of<br />
course, applies to internationalization as well.<br />
In this article you will learn about seven <strong>I18n</strong> best practices, including advice that will help<br />
organize your translations better and take full advantage of <strong>Rails</strong> power. I am assuming that you<br />
already know the basics behind internationalization and localization in <strong>Rails</strong>, but if you wish to<br />
refresh your knowledge, take a look at this official guide or our article describing the topic<br />
thoroughly. Have a nice reading!<br />
phraseapp.com | sales@phraseapp.com | +49-40-357-187-76 | twitter.com/phraseapp | facebook.com/phraseapp | linkedin.com/company/phraseapp
<strong>Rails</strong> Internationalization (<strong>I18n</strong>) - <strong>Seven</strong> <strong>Best</strong> <strong>Practices</strong> <strong>That</strong> <strong>You</strong> <strong>Should</strong> <strong>Know</strong> <strong>About</strong><br />
Split Translations<br />
Large applications may have hundreds of translations keys. If they are stored in a single file it<br />
becomes really hard to manage them, therefore it’s a good idea to split your messages into<br />
various files stored in separate folders. For example, you may have a folder named models<br />
storing translations for the models’ attributes, forms to translate form-related stuff etc:<br />
● locales<br />
○ models<br />
■ en.yml<br />
■ ru.yml<br />
○ forms<br />
■ en.yml<br />
■ ru.yml<br />
However, by default this is not going to work as <strong>Rails</strong> does not load translations from the nested<br />
directories. To fix this, you’ll need to add the following line of code inside your<br />
config/application.rb file:<br />
1 config.i18n.load_path += Dir[<strong>Rails</strong>.root.join('config', 'locales', '**', '*.{rb,yml}')]<br />
load_path is a setting to announce your custom translation files and it should do the trick.<br />
phraseapp.com | sales@phraseapp.com | +49-40-357-187-76 | twitter.com/phraseapp | facebook.com/phraseapp | linkedin.com/company/phraseapp
<strong>Rails</strong> Internationalization (<strong>I18n</strong>) - <strong>Seven</strong> <strong>Best</strong> <strong>Practices</strong> <strong>That</strong> <strong>You</strong> <strong>Should</strong> <strong>Know</strong> <strong>About</strong><br />
Take Advantage Of Nesting<br />
It is not advised to store all your translation messages on the same level of nesting (using flat<br />
naming). For example, this is not a recommended practice:<br />
● en:<br />
○ submit: ‘Submit’<br />
○ log_out: ‘Log Out’<br />
○ blog: ‘Browse Blog’<br />
○ errors: ‘Errors were found:’<br />
As you see, all the messages here are messed up <strong>–</strong> they relate to different pieces of the<br />
application but still they are mixed together. It is much better to introduce the parent keys to<br />
nest translations of the same type. For example, you might have something like that:<br />
● en:<br />
○ forms:<br />
■ submit: ‘Submit’<br />
■ errors: ‘Errors were found’<br />
○ main_menu:<br />
■ log_out: ‘Log Out’<br />
■ blog: ‘Browse Blog’<br />
This way the messages are grouped and it is easier to manage them. By the way, when<br />
translating <strong>Rails</strong> models you must follow this principle, as attributes’ names should be nested<br />
properly:<br />
1<br />
2<br />
3<br />
4<br />
5<br />
en:<br />
activerecord:<br />
attributes:<br />
category:<br />
name: 'Name'<br />
phraseapp.com | sales@phraseapp.com | +49-40-357-187-76 | twitter.com/phraseapp | facebook.com/phraseapp | linkedin.com/company/phraseapp
<strong>Rails</strong> Internationalization (<strong>I18n</strong>) - <strong>Seven</strong> <strong>Best</strong> <strong>Practices</strong> <strong>That</strong> <strong>You</strong> <strong>Should</strong> <strong>Know</strong> <strong>About</strong><br />
Employ “Lazy” Lookups<br />
One cool thing about <strong>I18n</strong> in <strong>Rails</strong> is that when naming and nesting your keys properly, you can<br />
write less code inside the views and controllers. How is that possible? Well, let’s say you have a<br />
view called about.html.erb stored inside the app/views/pages folder. Inside you wish to display<br />
the page’s title:<br />
1 <br />
However, this code can be as simple as:<br />
1 <br />
In order for this to work, you need to nest the title key under the pages.aboutscope:<br />
1<br />
2<br />
3<br />
pages:<br />
about:<br />
title: '<strong>About</strong> us'<br />
This works, because the scope is written properly: it contains the folder’s and the view’s name<br />
(pages and about respectively).<br />
The same approach works for controllers, so having the following translation in place:<br />
1<br />
orders:<br />
2<br />
3<br />
create:<br />
success: '<strong>You</strong>r order is created!'<br />
<strong>You</strong> may create a flash message easily:<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
class OrdersController < ApplicationController<br />
def create<br />
# ...<br />
flash[:success] = t(:success)<br />
end<br />
end<br />
It’s really convenient, so don’t be lazy to employ the “lazy” lookups!<br />
phraseapp.com | sales@phraseapp.com | +49-40-357-187-76 | twitter.com/phraseapp | facebook.com/phraseapp | linkedin.com/company/phraseapp
<strong>Rails</strong> Internationalization (<strong>I18n</strong>) - <strong>Seven</strong> <strong>Best</strong> <strong>Practices</strong> <strong>That</strong> <strong>You</strong> <strong>Should</strong> <strong>Know</strong> <strong>About</strong><br />
Enforce Available Locales<br />
By default each <strong>Rails</strong> application uses English locale and does not explicitly say which<br />
languages are supported. This is okay for applications that are not meant to be localized, but if<br />
you are planning to support multiple locales, then list them inside your config.rb file:<br />
1 config.i18n.available_locales = [:en, :de, :ru]<br />
This is convenient, because later you can take advantage of this array (fetch it using<br />
<strong>I18n</strong>.available_locales() method). For instance, this array may be employed to generate the proper<br />
routing scopes based on the languages’ codes:<br />
1 scope "(:locale)", locale: /#{<strong>I18n</strong>.available_locales.join("|")}/ do<br />
So you will get routes like /en/blog or /ru/shop. Even if the available locales change, the routes<br />
file will not require any changes.<br />
Moreover, this setting can come in handy when implementing a switching locale feature.<br />
Specifically, it can be used to check whether a user is requesting a supported locale and fallback<br />
to the default one if not. It does not really matter how exactly this feature is built, but suppose<br />
you have a method to extract locale data:<br />
1<br />
2<br />
3<br />
def extract_locale<br />
end<br />
parsed_locale = request.subdomains.first<br />
<strong>You</strong> may then easily check whether the requested locale is supported or not:<br />
1<br />
2<br />
3<br />
4<br />
def extract_locale<br />
parsed_locale = request.subdomains.first<br />
<strong>I18n</strong>.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil<br />
end<br />
phraseapp.com | sales@phraseapp.com | +49-40-357-187-76 | twitter.com/phraseapp | facebook.com/phraseapp | linkedin.com/company/phraseapp
<strong>Rails</strong> Internationalization (<strong>I18n</strong>) - <strong>Seven</strong> <strong>Best</strong> <strong>Practices</strong> <strong>That</strong> <strong>You</strong> <strong>Should</strong> <strong>Know</strong> <strong>About</strong><br />
available_locales() returns an array with symbols, so here we convert them to strings and then<br />
check whether the requested language code was found inside. <strong>You</strong> can read more on this<br />
technique at our Setting and Managing Locales in <strong>Rails</strong> <strong>I18n</strong> article.<br />
Utilize Localized Views<br />
Some developers tend to place all translations inside the YAML files regardless of their length<br />
and complexity. In some cases, however, that’s not really convenient. Suppose you have a page<br />
that looks totally different depending on the chosen language. Of course, you might do<br />
something like this:<br />
1<br />
<br />
2<br />
3<br />
<br />
4<br />
5<br />
<br />
Then you would have to define these keys, but the corresponding messages are too large:<br />
1<br />
2<br />
3<br />
welcome: 'Welcome!'<br />
special_offer: 'Here is our cool special offer! And then more text here...'<br />
about: 'Long text goes here.... and more text... even more...'<br />
Alternatively, you may take advantage of HTML translations but that’s not going to help a lot <strong>–</strong><br />
you still have long messages that are hard to maintain.<br />
What you can do instead, is stick with the localized views that allows you to store totally<br />
different content for each locale. They are employed by simply prefixing your views with the<br />
locale’s code, for example about.ru.html.erb and about.en.html.erb. <strong>Rails</strong> will render the proper<br />
view automatically depending on the value returned by the <strong>I18n</strong>.locale() method.<br />
phraseapp.com | sales@phraseapp.com | +49-40-357-187-76 | twitter.com/phraseapp | facebook.com/phraseapp | linkedin.com/company/phraseapp
<strong>Rails</strong> Internationalization (<strong>I18n</strong>) - <strong>Seven</strong> <strong>Best</strong> <strong>Practices</strong> <strong>That</strong> <strong>You</strong> <strong>Should</strong> <strong>Know</strong> <strong>About</strong><br />
Take Advantage Of Variables<br />
Another important thing some novice developers tend to forget about is the fact that you can<br />
pass variables to your translations in <strong>Rails</strong>. Suppose, for example, you wish to display how many<br />
new messages has the user received. Of course, you may simply employ string interpolation like<br />
this:<br />
1 <br />
But even though this piece of code is small, it looks overly complex and too ugly. Instead, let’s<br />
allow the message_count to accept a variable:<br />
1 message_count: "%{count} new messages"<br />
Then simply pass a hash to the t method as the second argument:<br />
1 <br />
Clean and simple. <strong>You</strong> can further extend this example by introducting pluralization rules that<br />
also rely on variables.<br />
phraseapp.com | sales@phraseapp.com | +49-40-357-187-76 | twitter.com/phraseapp | facebook.com/phraseapp | linkedin.com/company/phraseapp
<strong>Rails</strong> Internationalization (<strong>I18n</strong>) - <strong>Seven</strong> <strong>Best</strong> <strong>Practices</strong> <strong>That</strong> <strong>You</strong> <strong>Should</strong> <strong>Know</strong> <strong>About</strong><br />
Helper Methods<br />
<strong>Rails</strong> offers us a bunch of powerful helper methods that can be used to display datetime and<br />
month select boxes, convert numbers to currency and display distance of time in words. Some<br />
of them work as magic and can really save you from writing many lines of code every time.<br />
However, be warned that some of these methods are somewhat expensive in tems of processing<br />
time. distance_of_time_in_words is one of such methods <strong>–</strong> it says how much time has passed since<br />
the provided datetime. If you are planning to use this method extensively in some view (the<br />
typical example is the comments block to say how old the comment is), then maybe it is better<br />
to reconsider and put it away. <strong>You</strong> might think about employing fragment caching here but then<br />
you’ll need to constantly expire it. Time runs fast and the phrase “the comment was published X<br />
days ago” should be constantly updated.<br />
Another solution to this problem is performing the calculation on the client-side. For example,<br />
there is a great library called Moment.js that supports this feature (and many others).<br />
Stick With PhraseApp!<br />
Working with translations is hard: you might easily miss some translations for a specific<br />
language which will lead to user’s confusion. And so PhraseApp can make your life easier!<br />
Grab your 14-days trial. PhraseApp supports many different languages and frameworks,<br />
including JavaScript of course. It allows to easily import and export translations data. What’s<br />
cool, you can quickly understand which translation keys are missing because it’s easy to lose<br />
track when working with many languages in big applications. On top of that, you can<br />
collaborate with translators as it’s much better to have professionally done localization for your<br />
website.<br />
phraseapp.com | sales@phraseapp.com | +49-40-357-187-76 | twitter.com/phraseapp | facebook.com/phraseapp | linkedin.com/company/phraseapp
<strong>Rails</strong> Internationalization (<strong>I18n</strong>) - <strong>Seven</strong> <strong>Best</strong> <strong>Practices</strong> <strong>That</strong> <strong>You</strong> <strong>Should</strong> <strong>Know</strong> <strong>About</strong><br />
Conclusion<br />
So, in this article we have discussed some common best practices advised when working with<br />
<strong>I18n</strong> in <strong>Rails</strong>. I hope it will help you crafting maintainable and beautifil applications! Still, if you<br />
think that I have missed something, do not hesitate to share your opinion in the comments.<br />
Also, if you wish to learn more about internationalizing <strong>Rails</strong> applications, you may be<br />
interested in our article The Last <strong>Rails</strong> <strong>I18n</strong> Guide <strong>You</strong>’ll Ever Need.<br />
phraseapp.com | sales@phraseapp.com | +49-40-357-187-76 | twitter.com/phraseapp | facebook.com/phraseapp | linkedin.com/company/phraseapp
<strong>Rails</strong> Internationalization (<strong>I18n</strong>) - <strong>Seven</strong> <strong>Best</strong> <strong>Practices</strong> <strong>That</strong> <strong>You</strong> <strong>Should</strong> <strong>Know</strong> <strong>About</strong><br />
phraseapp.com<br />
sales@phraseapp.com<br />
+49-40-357-187-76<br />
ABC-Straße 4<br />
Hamburg, Germany<br />
phraseapp.com | sales@phraseapp.com | +49-40-357-187-76 | twitter.com/phraseapp | facebook.com/phraseapp | linkedin.com/company/phraseapp