Skip to content

Generating translated URLs

Geremia Taglialatela edited this page Sep 1, 2022 · 7 revisions

The following syntax may generate localized URLs in all available locales to link alternative versions of the current page for basic use cases:

<% I18n.available_locales.each do |locale| %>
  <%= link_to locale, url_for(locale: locale.to_s, only_path: true), rel: 'alternate', hreflang: locale.to_s %>
<% end %>

The only_path option generates relative URLs instead of absolute ones.

HEADS-UP!

  • The locale symbol must be converted to string (locale.to_s)
  • url_for is subject to limitations of Rails' url_for, so it may not work for all use cases (ref: #269)

To generate URL for a different page you can specify controller's action in the url_for call:

url_for(controller: '...', action: '...', locale: '...', only_path: true)

FriendlyId / Globalize

Localized slugs needs extra code, and you will need a custom implementation to deal with this use case.

This paragraph provides some example approaches. Feel free to move this code where you think it belongs (helpers, presenters...) and please edit this wiki if you have better suggestions.

Let's say we have the following:

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :set_article, only: :show

  private

  def set_article
    @article = Article.friendly.find(params[:id])
  end
end

# app/models/article.rb
class Article < ApplicationRecord
  extend FriendlyId

  translates :title, :slug

  friendly_id :title, use: %i[globalize slugged]
end

# config/routes.rb
Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  localized do
    resources :articles, only: %i[index show]
  end
end

Solution 1: Set translated urls in a before action

# app/controllers/articles_controller
class ArticlesController < ApplicationController
  before_action :set_article, only: :show
  append_before_action :set_localized_urls, only: :show

  private

  def set_article
    @article = Article.friendly.find(params[:id])
  end

  def set_localized_urls
    @localized_urls = I18n.available_locales.each_with_object({}) do |locale, obj|
      obj[locale] = I18n.with_locale(locale) { article_path(@article) }
    end
  end
end
<!-- app/views/articles/show.html.erb -->
<dl>
  <% @localized_urls.each do |locale, url| %>
    <dt><%= locale.to_s %></dt>
    <dd><%= link_to url, url, rel: 'alternate', hreflang: locale.to_s %></dd>
  <% end %>
</dl>

Solution 2: Use url_for in a localized block

<!-- app/views/articles/show.html.erb -->
<dl>
  <% I18n.available_locales.each do |locale| %>
    <dt><%= locale.to_s %></dt>
    <dd><%= I18n.with_locale(locale) { link_to url_for(@article), url_for(@article), rel: 'alternate', hreflang: locale.to_s } %></dd>
  <% end %>
</dl>

Solution 3: Use article_path in a localized block

<!-- app/views/articles/show.html.erb -->
<dl>
  <% I18n.available_locales.each do |locale| %>
    <dt><%= locale.to_s %></dt>
    <dd><%= I18n.with_locale(locale) { link_to article_path(@article), article_path(@article), rel: 'alternate', hreflang: locale.to_s } %></dd>
  <% end %>
</dl>

Ref: