LoginSignup
106
104

More than 5 years have passed since last update.

Rails の i18n でどの言語を見せるか判断する

Last updated at Posted at 2014-08-28

http://yurie.sexy/ のサイトの多言語化でいろいろ試行錯誤していましたがようやく落ち着いてきたので紹介してみます。そもそも i18n のやり方とか概念は他のページを参考にしてください。ここではどの locale で見せるかをどのように切り替えているかを紹介します。

やりたいこと

基本方針は下記のようにします。

  • params に locale が明示してあればその言語を使用する
  • 明示してなければ環境変数 HTTP_ACCEPT_LANGUAGE を見に行き、最初に現れた locale を使用する(厳密にやるなら q= も見に行くべきかも)

ただし、いずれも、

  • I18n::available_locales に存在しない locale の場合は無視する
  • 判定できなかったときは I18n.default_locale をセット

とします。今回のケースで対応する言語は :ja:en のみだったので、 :ja を見せるべきと判断できなかった人には全員 :en を出したいので、ぼくは日本人で日本人向けにつくったサイトだけれどもデフォルトは :en にしています。

コード例

有効な言語とデフォルト言語をしっかり設定しておきます。

config/application.rb
    I18n.available_locales = %i(ja en)
    I18n.enforce_available_locales = true
    I18n.default_locale = :en

そして、 ApplicationController に下記のように追加しました。

app/controllers/application_controller.rb
  before_action :set_locale

  private
  # I18n.locale をセットする
  def set_locale
    I18n.locale = locale_in_params || locale_in_accept_language || I18n.default_locale
  end

  # params の locale の値(優先すべき)
  # @return [Symbol]
  #   params から取った locale
  #   有効な値でなければ :en
  #   取得できなかった場合 nil
  def locale_in_params
    if params[:locale].present?
      params[:locale].to_sym.presence_in(I18n::available_locales) || I18n.default_locale
    else
      nil
    end
  end

  # 環境変数 HTTP_ACCEPT_LANGUAGE を順に検証し、最初に一致した有効な locale を返す
  # @return [Symbol]  環境変数 HTTP_ACCEPT_LANGUAGE から取った locale 。取得できなかった場合 nil
  def locale_in_accept_language
    request.env['HTTP_ACCEPT_LANGUAGE']
      .to_s # nil 対策
      .split(',')
      .map{ |_| _[0..1].to_sym }
      .select { |_| I18n::available_locales.include?(_) }
      .first
  end

  # 全リンクに locale 情報をセットする
  # @return [Hash] locale をキーとするハッシュ
  def default_url_options(options = {})
    { locale: I18n.locale }
  end

パスで locale を扱う

params[:locale] に値が入ればその方法は問いませんが、今回は下記のようにしてみました。パスの最初に 2 文字のものがあればそれを locale と解釈しています。

routes.rb
  scope '(/:locale)', constraints: { locale: /\w{2}/ } do
    get '/intro', controller: :home, action: :intro
    # :
    # :
  end

View に各言語へのリンクをつける

ユーザビリティ、 SEO 対策のため、「このページの locale を変更したリンクをつけたい」ということがあると思います。下記のようにすればできました。

link_to('日本語', url_for(params.merge(locale: 'ja')))

ぼくは下記のようにしています。 HAML をそのまま載せてますが、 Bootstrap の Navs と Dropdowns を組み合わせて 使っています。 iconFontAwesome::Sass の機能です。

app/views/layouts/application.html.haml
%ul.dropdown-menu{role: 'menu'}
  :ruby
    locales = {
      ja: '日本語',
      en: 'English',
      fr: 'Français'
    }
  - locales.each do |locale, label|
    - label = (I18n.locale == locale ? icon('check') : '') + label
    %li= link_to(label, url_for(params.merge(locale: locale)))

テストの例

Rspec の Request Spec を書いてみました。

spec/requests/locale_spec.rb
require 'rails_helper'

describe 'I18n' do
  context 'パスに言語指定がある場合' do
    it 'その言語とする' do
      get '/ja'
      expect(I18n.locale).to eq :ja
    end

    it '無効な言語の場合はデフォルトの英語とする' do
      get '/zh'
      expect(I18n.locale).to eq :en
    end
  end

  context '環境変数 HTTP_ACCEPT_LANGUAGE がある場合' do
    it 'HTTP_ACCEPT_LANGUAGE の言語を使用する' do
      get '/', nil, { HTTP_ACCEPT_LANGUAGE: 'ja' }
      expect(I18n.locale).to eq :ja
    end

    it '国コードがついていても正しく判断できる' do
      get '/', nil, { HTTP_ACCEPT_LANGUAGE: 'ja-JP' }
      expect(I18n.locale).to eq :ja
    end

    it '複数の言語がある場合、最初に一致した有効な言語を使用する' do
      get '/', nil, { HTTP_ACCEPT_LANGUAGE: 'zh,ja' }
      expect(I18n.locale).to eq :ja
    end

    it '有効な言語がない場合、デフォルトの英語とする' do
      get '/', nil, { HTTP_ACCEPT_LANGUAGE: 'zh,cn' }
      expect(I18n.locale).to eq :en
    end

    it 'パスに言語指定もある場合、パスの指定を優先する' do
      get '/ja', nil, { HTTP_ACCEPT_LANGUAGE: 'en' }
      expect(I18n.locale).to eq :ja
    end
  end

  context 'いずれもない場合' do
    it 'デフォルトで英語になる' do
      get '/'
      expect(I18n.locale).to eq :en
    end
  end
end

もっといいやり方があればコメントで教えてください!

参考文献

106
104
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
106
104