scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do
resources :posts
root to: redirect("/%{locale}/posts", status: 302)
end
root to: redirect("/#{I18n.default_locale}", status: 302), as: :redirected_root
get "/*path", to: redirect("/#{I18n.default_locale}/%{path}", status: 302), constraints: {path: /(?!(#{I18n.available_locales.join("|")})/).*/}, format: false
Let's take a look at the above code sample from config/routes.rb and will study what it does line by line.
-
Locale parameter will be added to posts resource url:
http://example.com/en/postsThe following part of code is responsible for that:
scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do resources :posts endNote, that available locales are taken from application config.
-
Root path with locale will redirect to localized version of posts (which is a default page)
http://example.com/ru→http://example.com/ru/postsThis is how we achieve it:
scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do root to: redirect("/%{locale}/posts", status: 302) endWe use
%{locale}here, which allows to use dynamic segment from the scope inside of redirect. Also it is very interesting how you can pass the status code for redirects. There are almost no mention about it in manuals. You simply need to specify additional parameterstatus: 302. The same result could be achieved in few different ways:scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do root to: redirect(status: 302) {|params, request| "/#{params[:locale]}/posts"} endor we can skip unused variables:
scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do root to: redirect(status: 302) {|params, _| "/#{params[:locale]}/posts"} endor we can even use dynamic segment inside a block:
scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do root to: redirect(status: 302) {|_, _| "/%{locale}/posts"} endI should note, that redirects without block as a parameter look prettier in the output of
bin/rake routes. Just compare:# root to: redirect(status: 302) {|_, _| "/%{locale}/posts"} root GET /:locale(.:format) redirect(302) {:locale=>/ru|en/}# root to: redirect("/%{locale}/posts", status: 302) root GET /:locale(.:format) redirect(302, /%{locale}/posts) {:locale=>/ru|en/} -
Root path will redirect to default locale
http://example.com/→http://example.com/en/
The following line is responsible for that:root to: redirect("/#{I18n.default_locale}", status: 302), as: :redirected_rootNote, that parameter
as: :redirected_rootis required, because we already have oneroot_pathand we can't have the second one in Rails 4 (in comparison to Rails 3). -
All paths with no locale specified will be redirected to the same paths with default locale:
http://example.com/posts→http://example.com/en/posts.
This is achieved thanks to this longest line:get "/*path", to: redirect("/#{I18n.default_locale}/%{path}", status: 302), constraints: {path: /(?!(#{I18n.available_locales.join("|")})/).*/}, format: falseParameter
/*pathis so-called globbing. This parameter includes all the characters in the remaining part of url, including slashes, GET parameters andformat: falseallows us to capture even.jsonor.htmlsignatures.Parameter
constraintchecks if path does not begin with locale and only in that case redirects to localized version. This way we will avoid infinite redirects for non-existing urls. I should note, that only GET requests are taken into account, because there shouldn't be any POST requests unless someone did some hacking or we have an error in our source code.
Let's take a look at another sample of routes
scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do
resources :posts
root to: "main#index"
end
root to: redirect("/#{I18n.default_locale}", status: 302), as: :redirected_root
get "/*path", to: redirect("/#{I18n.default_locale}/%{path}", status: 302), constraints: {path: /(?!(#{I18n.available_locales.join("|")})/).*/}, format: false
We don't have a redirect to the posts page and we have a separate main page here.
Language switching helper
That may seem strange, but the way how links are formed was changed in Rails 4 in comparison to Rails 3. Here is an example of language switching helper for Rails 3:
# app/helpers/application_helper.rb
module ApplicationHelper
def lang_switcher
content_tag(:ul, class: 'lang-switcher clearfix') do
I18n.available_locales.each do |loc|
concat content_tag(:li, (link_to loc, params.merge(locale: loc)), class: (I18n.locale == loc ? "active" : ""))
end
end
end
end
Using the above helper in Rails 3 will give us the usual /en and /ru links for the main page. But in Rails 4 the same code will give us /en/main/index and /ru/main/index instead. And this is how the very same helper should be implemented in Rails 4:
# app/helpers/application_helper.rb
module ApplicationHelper
def lang_switcher
content_tag(:ul, class: 'lang-switcher clearfix') do
I18n.available_locales.each do |loc|
locale_param = request.path == root_path ? root_path(locale: loc) : params.merge(locale: loc)
concat content_tag(:li, (link_to loc, locale_param), class: (I18n.locale == loc ? "active" : ""))
end
end
end
end
It took some time for me to understand how things work here, so I hope you've found something useful in this blog post too.