Previously I wrote about managing translations with gem i18n-tasks

Here’s how you can let users manually switch the current language.

First, be sure to have your application i18n defaults set:

# config/application.rb
  config.i18n.default_locale = :en
  config.i18n.available_locales = %i[en fr nl es de it pl pt ro ua]
  config.i18n.raise_on_missing_translations = true

Now you need to override the default_locale by setting I18n.locale = :de in application_controller.

🔔 Install gem rails-i18n to auto-translate stuff like time_ago_in_words.

Rubocop #

The Rails/I18nLocaleTexts helps you find untranslated strings. An error can look like this:

app/controllers/stripe/checkout_controller.rb:40:49: C: Rails/I18nLocaleTexts: Move locale texts to the locale files in the config/locales directory.
    redirect_to user_url(current_user), notice: "foo"

Set locale from URL #

Namespace ALL the translatable routes:

# config/routes.rb
  # scope "(:locale)", locale: /en|es/ do
  scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do
    resources :tags, only: %i[index show]
    resources :playlists, only: %i[index show]
  end

This way, the below routes are equivalent, and both respond to params[:locale]:

https://localhost:3000/en/posts
https://localhost:3000/posts?locale=en

default_url_options will append the current locale to all links in your app, so that the user is redirected properly:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_locale

  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
  end

  def default_url_options
    { locale: I18n.locale }
  end
end

Finally, add links to open page in a different locale:

# switch locales & redirect to root 
<%= link_to 'English', root_path(locale: :en) %>
<%= link_to 'Spanish', root_path(locale: :es) %>
# switch locales & redirect to current path 
<%= link_to 'English', url_for(locale: :en) %>
<%= link_to 'Spanish', url_for(locale: :es) %>
# switch locales & redirect to current path with params
<%= link_to 'English', url_for(request.parameters.merge(locale: :en)) %>
<%= link_to 'Spanish', url_for(request.parameters.merge(locale: :es)) %>

My current best approach:

<% I18n.available_locales.each do |locale| %>
  <%= link_to locale_to_flag(locale), url_for(locale:), class: (locale == I18n.locale ? "border-b" : "") %>
<% end %>

Set locale from session/cookies, or User preferences #

The official Rails Guides do not store locale in session/cookie.

Add language attribute to User model:

# terminal
rails g migration add_language_to_users language:string
# migration
  add_column :users, :language, :string, default: 'en'

Create a concern to set locale based on

# /app/controllers/application_controller.rb
  include SetLocale
# /app/controllers/concerns/set_locale.rb
module SetLocale
  extend ActiveSupport::Concern

  included do
    before_action :set_locale

    private

    def set_locale
      if params["locale"].present?
        language = params["locale"].to_sym
        session["locale"] = language
        if user_signed_in?
          current_user.update(language: language)
        end
        redirect_to(request.referrer || root_path)
      elsif session["locale"].present?
        language = session["locale"]
      else
        language = I18n.default_locale
      end

      if user_signed_in? && current_user.language.present?
        language = current_user.language
      end

      I18n.locale = if I18n.available_locales.map(&:to_s).include?(language)
        language
      else
        I18n.default_locale
      end
    end
  end
end

Finally, show the User links to set current locale:

<%#= @user.language %>
<%#= I18n.locale %>
<% I18n.available_locales.excluding(I18n.locale).each do |language| %>
  <%= link_to language, root_path(locale: language) %>
<% end %>