The Last Rails I18n Guide You’ll Ever Need

In this article we will discuss how to add support for I18n in a Rails application, where to store translations, what localized views are, how to format dates and times, how to introduce pluralization rules and more.

Internationalization (dubbed as I18n as there are exactly eighteen characters between the first “i” and the last “n”) means creating an application that can be adapted to various languages easily, without the need to do complex changes. This involves extracting various bits (strings, date and currency formats) out of a (Rails) application and then providing translations and formats for them. The latter is called localization and sometimes is dubbed as l10n. If your company is growing and seeking to go international, localization is an important step to do. In this Rails i18n guide, we will discuss the following topics:

  • Details around Rails i18n
  • Where to store translations
  • What localized views are
  • How to format dates, times, and numbers
  • How to introduce pluralization rules and more

By the end of this Rails i18n guide, you will have a solid understanding of using I18n with Rails and will be ready to employ the described techniques when building real-world apps. The source code for the demo app can be found at GitHub.

Our First Translation with Rails

The  Groundwork

So, I18n was the Rails’ core feature starting from version 2.2. It provides a powerful and easy-to-use framework that allows translating an app into as many languages as you need. To see it in action while discussing various concepts, let’s create a demo Rails application: https://gist.github.com/bodrovis/95f3d65ef345d42f215e4c7a9c374e14 For this article I am using Rails 6 but most of the described concepts apply to earlier versions as well. Go ahead and create a static pages controller called pages_controller.rb with a single action: https://gist.github.com/bodrovis/3f5c4d7e3c2802cb236c67713432dc03 views/pages/index.html.erb https://gist.github.com/bodrovis/cdfe2b706c28f24035b3157bd2afd532 Set up the root route inside config/routes.rb: https://gist.github.com/bodrovis/74174c1e9749b4df28ae29108e7673d0 So, we have the header on the main page that contains the hard-coded “Welcome!” word. If we are going to add support for multiple languages this is not really convenient — we need to extract this word somewhere and replace it with a more generic construct.

Storing Translations

By default, all translations live inside the config/locales directory, divided into files. They load up automatically as this directory is set as I18n.load_path by default. You may add more paths to this setting if you wish to structure your translations differently. For example, to load all the YAML and Ruby files from the locales directory and all nested directories, say: https://gist.github.com/bodrovis/ee5eb6083105a0195fbe96576627f696 inside your config/application.rb file. Our new app already contains an en.yml file inside the locales directory, so let’s change its contents to: config/locales/en.yml https://gist.github.com/bodrovis/c271c366de3b258617945bb32db5a777 yml extension stands for YAML (Yet Another Markup Language) and it’s a very simple format of storing and structuring data. The top-most en key means that inside this file we are storing English translations. Nested is the welcome key that has a value of “Welcome!”. This string is an actual translation that can be referenced from the application. Here is a nice guide to naming your keys and a guide to I18n best practices in Rails. The core method to lookup translations is translate or simply t: views/pages/index.html.erb https://gist.github.com/bodrovis/94b66d8c0c01d3714f64b26871f1fcc7 Now instead of hard-coding an English word, we tell Rails where to fetch its translation. welcome corresponds to the key introduced inside the en.yml file. English if the default language for Rails applications so when reloading the page you’ll see the same “Welcome!” word. Nice!

Adding Support for an Additional Language in Rails

Passing Locale Data

Surely you are eager to check how this all is going to work with the support for multiple languages. In order to do that we need a way to provide the language’s name to use. There are multiple options available:

  • Provide language’s name as a GET parameter (example.com?locale=en)
  • Specify it as a part of a domain name (en.example.com)
  • Provide it as a part of a URL (example.com/en/page). Technically, that’s a GET parameter as well.
  • Set it based on the user agent sent by the browser
  • Adjust it basing on the user’s location (not really recommended)

To keep things simple we will stick with the first solution. If you would like to learn about other techniques, take a look at our “Setting and Managing Locales in Rails” guide. Firstly, introduce a new before_action inside theApplicationController: application_controller.rb https://gist.github.com/bodrovis/4ddeef4dcc90dee2c78f9a3e3bcf381c The idea is simple: we either fetch a GET parameter called locale and assign it to the I18n.locale option or read the default locale which, as you remember, is currently set to en.

Available Locales

Now try navigating to http://localhost:3000?locale=de and… you’ll get an InvalidLocale error. Why is that? To understand what’s going on, add the following contents to the index page: views/pages/index.html.erb https://gist.github.com/bodrovis/8e5c98adc2e9e6814c83e97cb52409d5 Next, reload the page while stripping out the ?locale=de part. You’ll note that only [:en] is being rendered meaning that we do not have any other available locales available at all. To fix that, add a new gem into the Gemfile: Gemfile https://gist.github.com/bodrovis/8ea801962b497e4a80ddbdf8f4933fbe rails-i18n provides locale data for Ruby and Rails. It stores basic translations like months’ and years’ names, validation messages, pluralization rules, and many other ready-to-use stuff. Here is the list of supported languages. Run: https://gist.github.com/bodrovis/37e183fc99eae36427cd87206eb789a7 Then boot the server and reload the page once again. Now you’ll see a huge array of supported languages. That’s great, but most likely you won’t need them all, therefore let’s redefine the available_locales setting: config/application.rb https://gist.github.com/bodrovis/2faf6d09262fafca639ae20b08a1f566 Now we support English and Russian languages. Also, while we are here, let’s set a new default locale for our app for demonstration purposes: config/application.rb https://gist.github.com/bodrovis/8091ef0e3cd3b0d3a3f995e9c47d9756 Don’t forget to reload the server after modifying this file!

Switching Locale

The code for the before_action should be modified to check whether the requested locale is supported: application_controller.rb https://gist.github.com/bodrovis/7753d812b6a46ee86daa2c45dbe81fa4 As long as we’ve used symbols when defining available locales, we should convert the GET parameter to a symbol as well. Next, we check whether this locale is supported and either set it or use the default one. We should also persist the chosen locale when the users visit other pages of the site. To achieve that, add a new default_url_options method to the application_controller.rb : https://gist.github.com/bodrovis/bbd9891b072a5261df7ffa158b65320c Now all links generated with routing helpers (like posts_path or posts_url) will contain a locale GET parameter equal to the currently chosen locale. The users should also be able to switch between locales, so let’s add two links to our application.html.erb layout: https://gist.github.com/bodrovis/902aad0eee7cb657f2d8677cb2936389

Providing More Translations

Now when you switch to the Russian language (or any other language you added support for, except for English), you’ll note that the header contains the “Welcome” word, but without the “!” sign. Use Developer Tools in your browser and inspect the header’s markup: https://gist.github.com/bodrovis/dd65bfec7d02878364ae1f237d68c05a What happens is Rails cannot find the translation for the welcome key when switching to Russian locale. It simply converts this key to a title and displays it on the page. You may provide a :default option to the t method in order to say what to display if the translation is not available: https://gist.github.com/bodrovis/a8301c43960d8581b71f5c34a003554a To fix that, let’s create a new translations file for the Russian locale: config/locales/ru.yml https://gist.github.com/bodrovis/1824c581a24bf920f129bf8301e6f60f Now everything should be working just great, but make sure you don’t fall for some common mistakes developers usually do while localizing an app. Also note that the t method accepts a :locale option to say which locale to use: t 'welcome', locale: :en.

Using Scopes

Having all translations residing on the same level of nesting is not very convenient when you have many pages in your app: https://gist.github.com/bodrovis/56e3ea80d173f6af3b08948dfeedf36a As you see, those translations are messed up and not structured in any way. Instead, we can group them using scopes: config/locales/en.yml https://gist.github.com/bodrovis/d66d2a4f3b208bb30c6126721384ea45 config/locales/en.yml https://gist.github.com/bodrovis/a2857c5b895250bdf8d77888b0d7b8a9 So now the welcome key is scoped under the pages.index namespace. To reference it you may use one of these constructs: https://gist.github.com/bodrovis/c0683c09a9093b35e61d4d9303677d09 What’s even better, when the scope is named after the controller (pages) and the method (index), we can safely omit it! Therefore this line will work as well: https://gist.github.com/bodrovis/6fc2b8d2de4193b6a8c090ec174d0189 when placed inside the pages/index.html.erb view or inside the index action of the PagesController. This technique is called “lazy lookup” and it can save you from a lot of typing. Having this knowledge, let’s modify the views/pages/index.html.erb view once again: https://gist.github.com/bodrovis/a5019c37e8bd8cd04ac924241b3d95c1

Localized Views

Now, if your views contain too much static text, you may introduce the localized views instead. Suppose, we need to create an “About Us” page. Add a new route: https://gist.github.com/bodrovis/579e35213266b9ae3947b5f6a120dfa0 And then create two views with locale’s title being a part of the file name: views/pages/about.en.html.erb https://gist.github.com/bodrovis/0abc026e381f862b0b85b0eb6e8abf1e views/pages/about.ru.html.erb https://gist.github.com/bodrovis/dc5864c1d5dbb1514160ab75ca55a4a8 Rails will automatically pick the proper view based on the currently set locale. Note that this feature also works with ActionMailer!

HTML Translations

Rails i18n supports HTML translations as well, but there is a small gotcha. Let’s display some translated text on the main page and make it semibold: views/pages/index.html.erb https://gist.github.com/bodrovis/ddc9c315b8480fc41ad7d9ad0d634fde config/locales/en.yml https://gist.github.com/bodrovis/82cfb4915d1b54f4fa556f7d33317b1d config/locales/ru.yml https://gist.github.com/bodrovis/c980b09185aae8e3e4f12009b41696a4 This, however, will make the text appear as is, meaning that the HTML markup will be displayed as a plain text. To make the text semibold, you may say: https://gist.github.com/bodrovis/d0abc45dedea28642ea75e9efca9d105 or add an _html suffix to the key: https://gist.github.com/bodrovis/b3d770bdc61f8bcfde183593a45f9220 Don’t forget to modify the view’s code: https://gist.github.com/bodrovis/1126b964e86f065aee4929e1b90e65af Another option would be to nest the html key like this: https://gist.github.com/bodrovis/b5b1698d208b8ee3f381b7993ecb7d7f and then say: https://gist.github.com/bodrovis/2646107d7e41dd95316c5a03206b2543

Translations for ActiveRecord

Scaffolding New Resources

Now suppose we wish to manage blog posts using our application. Create a scaffold and apply the corresponding migration: https://gist.github.com/bodrovis/bf2a8314e047a1315638fa2037690942 When using Rails 3 or 4, the latter command should be: https://gist.github.com/bodrovis/079804f91352adae07b611e4ca16aca6 Next add the link to create a new post: views/pages/index.html.erb https://gist.github.com/bodrovis/aae477c590038c54adbe239048fc743e Then add translations: config/locales/en.yml https://gist.github.com/bodrovis/9d9a27f1780a431015bddb2ae22b0b32 config/locales/ru.yml https://gist.github.com/bodrovis/a15c7c4360e8279a7ee04e11e8a38811 Boot the server and click this new link. You’ll see a form to create a new post, but the problem is that it’s not being translated. The labels are in English and the button says “Создать Post”. The interesting thing here is that the word “Создать” (meaning “Create”) was taken from the rails-i18n gem that, as you remember, stores translations for some common words. Still, Rails has no idea how to translate the model’s attributes and its title.

Adding Translations for ActiveRecord

To fix this problem, we have to introduce a special scope called activerecord: config/locales/ru.yml https://gist.github.com/bodrovis/f4c7f1a93883862b9d916d4bb62429fb config/locales/en.yml https://gist.github.com/bodrovis/91ba36ae0db71542524e7b9cb438966b So the models’ names are scoped under the activerecord.models namespace, whereas attributes’ names reside under activerecord.attributes.SINGULAR_MODEL_NAME. The label helper method is clever enough to translate the attribute’s title automatically, therefore, this line of code inside the _form.html.erb partial does not require any changes: https://gist.github.com/bodrovis/0b6234d61b1af35fdf4d349554f529c0 Next provide some basic validation rules for the model: models/post.rb https://gist.github.com/bodrovis/49cbd82276195c118dfb33b7e0fe7725 After that try to submit an empty form and note that even the error messages have proper translations thanks to the rails-i18n gem! The only part of the page left untranslated is the “New Post” title and the “Back” link — I’ll leave them for you to take care of.

Translating E-mail Subjects

You may easily translate subjects for your e-mails sent with ActionMailer. For example, create PostMailer inside the mailers/post_mailer.rb file: https://gist.github.com/bodrovis/2e57d46efde3776a056c1c285e45dae0 Note that the subject parameter is not present but Rails will try to search for the corresponding translation under the post_mailer.notify.subject key: en.yml https://gist.github.com/bodrovis/bf721866b06330b9694c18cc3ddfdaae ru.yml https://gist.github.com/bodrovis/6134f04fc4e693fc6f89952975962096 The subject may contain interpolation, for example: https://gist.github.com/bodrovis/087bc47594e9dd469b9555fe6a0bf015 In this case, utilize the default_i18n_subject method and provide value for the variable: https://gist.github.com/bodrovis/466f3bb4b1788ce5511a6aa5765e0686

Date and Time

Now let’s discuss how to localize date and time in Rails.

Some More Ground Work

Before moving on, create a post either using a form or by employing db/seeds.rb file. Also add a new link to the root page: pages/index.html.erb https://gist.github.com/bodrovis/2cb705e2c98c04a8a53cd6dd3130e9d7 Then translate it: config/locales/en.yml https://gist.github.com/bodrovis/26dbf0d3fc93d1d0bb3e92255ead2a92 config/locales/ru.yml https://gist.github.com/bodrovis/bac2210b4ded844bbcb8de7bdbd875c6 Tweak the posts index view by introducing a new column called “Created at”: views/posts/index.html.erb https://gist.github.com/bodrovis/aa3e2dbf4c2286022813fab4fb4032e9

Localizing Datetime

We are not going to translate all the columns and titles on this page — let’s focus only on the post’s creation date. Currently, it looks like “2016-08-24 14:37:26 UTC” which is not very user-friendly. To localize a date or time utilize the localize method (or its alias l): https://gist.github.com/bodrovis/9df9b482f5558207c77491683b3e2696 The result will be “Ср, 24 авг. 2016, 14:37:26 +0000” which is the default (long) time format. The rails-i18n gem provides some additional formats — you can see their masks here (for the dates) and here (for the times). If you have used Ruby’s strftime method before, you’ll notice that those format directives (%Y, %m, and others) are absolutely the same. In order to employ one of the predefined formatting rules, say l post.created_at, format: :long. If you are not satisfied with the formats available by default, you may introduce a new one for every language: https://gist.github.com/bodrovis/ce22f9556420f623133de5203e4ba720 Now this format can be used inside the view by saying l post.created_at, format: :own. If, for some reason, you don’t want to define a new format, the mask may be passed directly to the :format option: l post.created_at, format: '%H:%M:%S, %d %B'. Just like the t method, l also accepts the :locale option: l post.created_at, locale: :en. The last thing to note here is that the rails-i18n gem also has translations for the distance_of_time_in_words, distance_of_time_in_words_to_now, and time_ago_in_words methods, so you may employ them as well: time_ago_in_words post.created_at.

Localizing Numbers

Rails has an array of built-in helper methods allowing to convert numbers into various localized values. Let’s take a look at some examples.

Converting Numbers to Currency

In order to convert a given number to currency, use a self-explanatory number_to_currency method. Basically, it accepts an integer or a float number and turns it into a currency string. It also accepts a handful of optional parameters:

  • :locale — locale to be used for formatting (by default, the currently set locale is used)
  • :unit — denomination for the currency, default is $. This setting obeys the :locale setting therefore for the Russian locale, roubles will be used instead
  • :delimeter — thousands delimiter
  • :separator — separator between units

The rails-i18n gem provides formatting for common currencies (based on the set locale), therefore you may simply say: https://gist.github.com/bodrovis/3985e0d1bb00ae8b5b88a06212237bda It will print “$1,234,567,890.50” for English locale and “1 234 567 890,50 руб.” for Russian.

Converting Numbers to Human Format

What’s interesting, Rails even has a special number_to_human method which convert the given number to human-speaken format. For instance: https://gist.github.com/bodrovis/6471eff2731fdae1d6af4d12f00c6be2 This string will produce “1.23 Million” for English and “1,2 миллион” for Russian locale. number_to_human accepts a handful of arguments to control the resulting value.

Converting Numbers to Phones

Numbers may also be converted to phone numbers with the help of number_to_phone method: https://gist.github.com/bodrovis/4ccac46b16fd796cf9e4bf9d96e3c39d This will produce “123-555-1234” string but the resulting value may be further adjusted with arguments like :area_code, :extension, and others.

Converting Numbers to Sizes

Your numbers may be easily converted to computer sizes with the help of number_to_human_size method: https://gist.github.com/bodrovis/0ca9b64fd86fcd9a7e132436fcaeb86a This will produce “1.15 GB” for English and “1,1 ГБ” for Russian locale.

Pluralization Rules and Variables

Different languages have, of course, different pluralization rules. For example, english words are pluralized by adding an “s” flection (except for some special cases). In Russian pluralization rules are much complex. It’s up to developers to add properly pluralized translations but Rails does heavy lifting for us. Suppose we want to display how many posts the blog contains. Create a new scope and add pluralization rules for the English locale: locales/en.yml https://gist.github.com/bodrovis/4061463c4c4cca00282e6f81445d9cb8 The %{count} is the variable part — we will pass a value for it when using the t method. As for the Russian locale, things are a bit more complex: locales/ru.yml https://gist.github.com/bodrovis/200dee885004f9d613f04bc2e35788f3 Rails automatically determines which key to use based on the provided number. Now tweak the view: views/posts/index.html.erb https://gist.github.com/bodrovis/d15377743bac337cb143a054d098f93d Note that there is also a config/initialializers/inflections.rb file that can be used to store inflection rules for various cases. Lastly, you may provide any other variables to your translations utilizing the same approach described above: https://gist.github.com/bodrovis/fdf4291664591a60a6123046b02f5cdd Then just say: https://gist.github.com/bodrovis/335e50f63456c04782f2d8485b49ba2c

Phrase Makes Your Life Easier!

Keeping track of translation keys as your app grows can be quite tedious, especially if you support many languages. After adding some translation you have to make sure that it does present for all languages. Phrase is here to help you!

A (Very) Quick Start

  • Create a new account (a 14 days trial is available) if you don’t have one, fill in your details, and create a new project (I’ve named it “I18n demo”)
  • Navigate to the Dashboard – here you may observe summary information about the project
  • Open Locales tab and click Upload File button
  • Choose one of two locale files (en.yml or ru.yml). The first uploaded locale will be marked as the default one, but that can be changed later
  • Select Ruby/Rails YAML from the Format dropdown
  • Select UTF-8 for the Encoding
  • You may also add some Tags for convenience
  • Upload another translations file

Now inside the Locales tab you’ll see two languages, both having a green line: it means that these locales are 100% translated. Here you may also download them, edit their settings and delete.

Adding Another Language

Next suppose we want to add support for German language and track which keys need to be translated.

  • Click Add locale button
  • Enter a name for the locale (“de”, for example)
  • Select “German – de” from the Locale dropdown
  • Click Save

Now the new locale appears on the page. Note there is a small message saying “9 untranslated” meaning that you will have keys without the corresponding translation. Click on that message and you’ll see all the keys we’ve added while building the demo app. Now simply click on these keys, add a translation for them, and click Save (this button may be changed to Click & Next). Note that there is even a History tab available saying who, when and how changed translation for this key. When you are done return to the Locales tab and click the “Download” button next to the German locale: you’ll get a YAML file. Copy it inside the locales directory and translation is ready! Localization is not only about translation and you may be not that familiar with a language you plan to support. But that’s not a problem – you can ask professionals to help you! Select Order Translations from the dropdown next to the locale, choose provider, provide details about your request and click “Calculate price”. Submit the request and your translation will be ready soon! You can read more about professional translations and average prices.

Conclusion

In this Rails i18n guide, we discussed internationalization and localization in Rails. We set up basic translations, introduced localized views, translated ActiveRecord attributes and models, localized date and time, and also provided some pluralization rules. Hopefully, now you are feeling more confident about using Rails i18n. For additional information, you may refer to this official Rails guide and read up some info on rails-i18n GitHub page. Storing translations inside the YAML files is not the only approach in Rails, so you may be interested in a solution called Globalize – with the help of it you may store translations in the database.

The Last Rails I18n Guide You’ll Ever Need
4.8 (95.17%) 29 votes
Author
Ilya Phrase Content Team
Comments