Gems such as route_translator — which extends Rails DSL to the level of routing using a particular localized directive — come to our aid to facilitate the process of internationalizating URLs:

# config/routes.rb localized do get "/about" to: 'static#about', as: :about resources :posts, only: [:show] end

The route translations are specified in local files:

# config/locales/fr.yml fr: routes: posts: nouvelles about: a-propos-de-nous # config/locales/en.yml en: routes: posts: posts about: about # config/locales/it.yml it: routes: posts: articoli about: chi-siamo

For every route indicated within the block, a route helper specified for each language will be generated, as will a helper capable of dynamically selecting the most correct route for the active language:

about_it_path # => /chi-siamo about_en_path # => /en/about about_fr_path # => /fr/a-propos-de-nous about_path # => dipendente dalla lingua corrente

These helpers make it very simple to generate a language switch:

/ app/views/static/about.html.slim - content_for(:switch_locale) do ul.switch_locale - I18n.available_locales.each do |locale| li= I18n.with_locale(locale) do = link_to "Switch to #{locale}", about_url

Now, let's imagine a site with twenty routes to be translated. Will we have to replicate a similar snipped for each view? That would hardly be a very DRY approach. Why not just use the url_for method?

/ app/views/layout/application.html.slim ul.switch_locale - I18n.available_locales.each do |locale| li= I18n.with_locale(locale) do = link_to "Switch to #{locale}", url_for(locale: locale)

This trick works so long as we're working with models that don't also require the slugs to be translated (for example, by copying globalize + friendly_id ):

/ /app/views/posts/show.html.slim = I18n.with_locale(:it) { post.slug } # => "il-mio-primo-post" = I18n.with_locale(:en) { post.slug } # => "my-first-post" = I18n.with_locale(:fr) { post.slug } # => "mon-premier-nouvell" = params.inspect / => {"controller" => "posts", "action" => "show", "id" => "my-first-post"} - %i(it en fr).each do |locale| = I18n.with_locale(locale) { url_for(locale: locale) } / => /articoli/my-first-post / => /en/posts/my-first-post / => /fr/nouvelles/my-first-post

What's happened here? Well, the url_for method constructs the URL based on the keys (in our case, local) that it has received and the keys of the global hash params — (in our case, action and id ).

Unfortunately for us, the id key contains the value of the slug relative to the current page ( "my-first-post" ). The post_path route generated by route_translator is able to translate the part of the URL related to controller and action, but it can't do much with the id parameter that it receives as input, and that isn't recognizable as the Post model that it originates from.

We can get around this inconvenience by making the local switcher slightly more configurable:

/ app/views/layout/application.html.slim ul.switch_locale - I18n.available_locales.each do |locale| li= I18n.with_locale(locale) do - url = yield(:current_page_url) || url_for(locale: locale) = link_to "Switch to #{locale}", url / app/views/posts/show.html.slim - content_for(:current_page_url) { post_url(@post) }

Doing it this way we are free, in the case of the routes with translated slugs, to specify a particular method to use for URL generation: