Setting and Managing Locales in Rails i18n

We discuss the different ways to manage locales across requests in Rails i18n

One of the previous articles dealt with I18n in Rails. We talked about storing and fetching translations, localizing the app and other useful stuff. In addition to rails i18n we have not discussed the different ways to manage locales across requests. By default, Rails is going to use locale set in the I18n.default_locale (which is :en or any other value you define in the configuration) or the value from I18n.locale if it was explicitly defined. Of course, if an application supports multiple languages, its users need a way to change their locale and their choice should be persisted. Therefore, in this article we will explore the following solutions:

  • Provide locale’s name as a GET parameter (
  • Provide it as a part of a domain name (
  • Set locale based on the user agent sent by the browser
  • Set locale based on the user’s location

The source code for the demo app is available at GitHub. By the way, if you are only starting to learn Rails, here is the great list of helpful resources.

Preparing the Rails App

In this demo I am going to use Rails but the described concepts apply to older versions as well. To start off, create a new application without the default testing suite:

In order to provide support for additional languages, include the rails-i18n gem into your Gemfile: Gemfile

Install it

I am going to provide support for English and Polish languages in this demo, but you may pick anything else. Let’s explicitly define supported locales: config/application.rb

Also quickly set up two small pages managed by the PagesController: pages_controller.rb



Don’t forget that the t method is an alias for I18n.translate and it looks up translation based on the provided key. Here are our translations: config/locales/en.yml


As long as we are naming these keys based on the controller’s and the view’s names, inside the .html.erb file we can simply say t('.title') omitting the pages.index or pages.about parts. Set the routes: config/routes.rb

Lastly, provide the links to change the site’s locale (URLs will be empty for now): shared/_change_locale.html.erb

Render this partial inside the layout: layouts/application.html.erb

Nice! Preparations are done and we can proceed to the main part.

Setting Locale Based on Your Toplevel Domain

The first solution is setting the locale based on the first level domain’s name. For example will render an English version of the site, whereas the Polish version. This solution has a number of advantages and probably the most important one is that users can easily understand what language they are going to use. Still, if your website supports many locales, purchasing multiple domains can be costly. In order to test this solution locally, you’ll need to configure your workstation a bit by editing the hosts file. This file is found inside the etc directory (for Windows, that’ll be %WINDIR%\system32\drivers\etc). Edit it by adding

Now visiting and should navigate you to our Rails app. A very common place to set locale is the before_action inside the ApplicationController: application_controller.rb

To grab the requested host name, use application_controller.rb

In this method we strip the last part of the domain’s name (com, pl etc) and check whether the requested locale is supported. If yes – return it, otherwise say nil. Now tweak the links to change locale: shared/_change_locale.html.erb

To make it a bit more user-friendly let’s append the current path to the URL: shared/_change_locale.html.erb

Now you may test the result!

Employing Subdomain

Of course, instead of purchasing multiple first level domains, you may register subdomains in your domain zone, for example and The extract_locale method should be changed like this: application_controller.rb

Of course, the links will look a bit different as well: shared/_change_locale.html.erb

Setting Locale Based on HTTP GET Parameters

Another very common approach is employing the HTTP GET params, for example localhost:3000?locale=en. This will require us to change the extract_locale method once again: application_controller.rb

The problem, however, is the need to persist the chosen locale between the requests. Of course, you may say link_to root_url(locale: I18n.locale) every time, but that’s not the best idea. Instead you can rely on the default_url_options method that sets default params for the url_for method and other methods that rely on it: application_controller.rb

This will make your route helpers automatically include the ?locale part. However, I do not really like this approach, mostly because of that annoying GET param. Therefore let’s discuss yet another solution.

Using Routes’ Scopes

As you probably recall, routes can be scoped and this feature may be used to persist the locale’s name easily: config/routes.rb

By wrapping :locale with round brackets we make this GET param optional. locale: /en|pl/ sets the regular expression checking that this param can only contain en or pl, therefore any of these links is correct:

  • http://localhost:3000/about
  • http://localhost:3000/en/about
  • http://localhost:3000/pl/about

Modify the links to switch locale: shared/_change_locale.html.erb

In my point of view, this solution is much tidier than passing locale via the ?locale GET param.

Inferring Locale Based on User’s Settings

When locale was not set explicitly, you will fallback to the default value set in I18n.default_locale but we may change this behaviour. To get an implicit locale, you may either use HTTP headers or information about a visitor’s location, so let’s see those two approaches in action now.

Using HTTP headers

There is a special HTTP header called Accept-Language that browsers set based on the language preferences on a user’s device. Its contents usually looks like en-US,en;q=0.5, but we are interested only in the first two characters, therefore the extract_locale method can be tweaked like this: application_controller.rb

There is a great gem http_accept_language that acts as Rack middleware and helps you to solve this problem more robustly.

Employing User’s Location

Another approach would be to set the default locale based on the user’s location. This solution is usually considered unreliable and generally not recommended, but for completeness sake let’s discuss it as well. In order to fetch user’s location let’s employ a gem called geocoder that can be used for lots of different tasks and even provides hooks for ActiveRecord and Mongoid. In this demo, however, things will be much simpler. First of all, add this new gem Gemfile

and run

Now we can take advantage of request.location.country_code to see the user’s country. The resulting string, however, is in capital case, so we are going to downcase it. Here is the corresponding code: application_controller.rb

The only problem here is that you won’t be able to test it locally, as request.location.country_code will always return “RD”. Still, you may deploy your app on Heroku (this will take literally a couple of minutes) and test everything there by utilizing open proxy servers. Once again though I want to remind you that setting user’s locale based on its location is not considered a recommended practice, because someone may, for example, be visiting another country during a business trip.

Phrase and Managing Translations

Of course, introducing the mechanism to switch and persist locale is very important for any multi-language app, but that makes little sense if you have no translations. And Phrase is here to make the process of managing translations much easier! You may try Phrase for free for 14 days right now. It supports a huge list of different languages and frameworks from Rails to JavaScript and allows to easily import and export translations data. What’s cool, you can quickly understand which translation keys are missing because it’s easy to lose track when working with many languages in big applications. Therefore, I really encourage you to give it a try!


In this application we covered different ways to switch and persist locale data among requests. We’ve seen how locale can be passed as a part of domain’s name and as a part of URL. Also, we’ve talked about inferring locale based on HTTP header and on user’s location. Hopefully, this article was useful and interesting for you. I thank you for staying with me and happy coding!

Setting and Managing Locales in Rails i18n
4.7 (93.33%) 12 votes
Ilya Phrase Content Team