415
Help us understand the problem. What are the problem?

posted at

updated at

[初学者]Railsのi18nによる日本語化対応

目的

  • 日本語化対応した時のメモ
  • 今後、railsを学ぶ方に向けての参考に役立てる。

条件

  • ruby 2.5.1
  • Rails 5.0.7

リスト

  1. デフォルトの言語を日本語に設定する
  2. gem 'rails-i18n'をインストール
  3. 複数のロケールファイルが読み込まれるようpathを通す
  4. config/locales以下にロケールファイルを配置
  5. ja.ymlに日本語を設定する
  6. 設定した日本を表示させる
  7. 様々な記述方法

1. デフォルトの言語を日本語に設定する

config/application.rb
require_relative 'boot'
require 'rails/all'

Bundler.require(*Rails.groups)

module BoardApp
  class Application < Rails::Application
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local

    # 以下の記述を追記する(設定必須)
    # デフォルトのlocaleを日本語(:ja)にする
    config.i18n.default_locale = :ja

  end
end

2. gem 'rails-i18n'をインストール

Gemfile
# rails5系なら以下を記述
# For 5.0.x, 5.1.x and 5.2.x
gem 'rails-i18n', '~> 5.1' 

# rails4系なら以下を記述
# For 4.0.x
gem 'rails-i18n', '~> 4.0' 

上記のgemを導入することによって、
Railsを日本語で使う場合のデフォルトのロケールファイルを「svenfuchs/rails-i18n」をダウンロードしなくても使えるようになります。

Gmeを使わない場合は下記コマンドを実行

config/locales/ja.ymlにダウンロード
curl -s https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml -o config/locales/ja.yml

1 の作業config.i18n.default_locale = :ja を設定しないと反映されないので設定必須!

3. i18nの複数ロケールファイルが読み込まれるようpathを通す

config/application.rb

require_relative 'boot'
require 'rails/all'

Bundler.require(*Rails.groups)

module BoardApp
  class Application < Rails::Application
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local
    config.i18n.default_locale = :ja # デフォルトのlocaleを日本語(:ja)にする

    # # 以下の記述を追記する(設定必須)
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]

  end
end

上記を記述することによって、複数のローケルファイルが読み込まれるようになった

4. config/locales以下にロケールファイルを配置

次に、views / modelによってロケールファイルを分けます。(今回controllerは省略)

ファイル構造(例)
config
└── locales
    ├── model.ja.yml  # modelは1つのファイルで管理します。
    └── views         # views関連のファイルはここで管理する
        ├── admin
        │   ├── dashboards
        │   │   └── ja.yml
        │   ├── ja.yml
        │   ├── user_sessions
        │   │   └── ja.yml
        │   └── users
        │       └── ja.yml
        ├── boards
        │   └── ja.yml
        ├── dashboards
        │   └── ja.yml
        ├── password_resets
        │   └── ja.yml
        ├── shared
        │   └── ja.yml
        ├── user_sessions
        │   └── ja.yml
        └── users
            └── ja.yml

5. ja.ymlに日本語を設定する

viewsの日本語化を設定する

# app/views/users/index.html.erb
<%= t ".title" %>

# app/views/users/show.html.erb
<%= t ".title", user_name: current_user.full_name %>

# app/views/users/edit.html.erb
<%= t ".title", user_name: current_user.full_name %>
config/locales/views/users/ja.yml

# ビューはビューを格納しているフォルダ名を起点にし、ビュー名毎に記述する。
# インデント(2space)でpathを制御している

ja:
  users:
    index:
      title: 'ユーザ一覧'
    show:
      # 引数の指定もできる。
      title: '%{user_name}さんのユーザ情報'
    edit:
      # view側で t(.titile), user_name: @user.name みたいな感じで設定できる
      title: '%{user_name}さんのユーザ情報を編集'

modelの日本語化を設定する

config/locales/model.ja.yml

# モデルは全て activerecord 以下に記述する。
# これにより、User.model_name.human / User.human_attribute_name({attr_name})で使用可能。

ja:
  activerecord:
    models:
      # view側: User.model_name.human => "ユーザ" / t("activerecord.models.user")と同じ
      user: ユーザー 
      board: 掲示板
    # model毎に定義したいattributesを記述
    attributes:
        user:
          id: ID
          # view側: User.human_attribute_name :name => "名前" / t("activerecord.attributes.user.name")と同じ
          first_name: 名前
          last_name: 
          email: メールアドレス
          file: プロフィール画像
          crypted_password: パスワード
  # 全てのmodelで共通して使用するattributesを定義
  attributes:
    created_at: 作成日
    updated_at: 更新日

Controllers の日本語化を設定する

app/controllers/messages_controller.rb
# app/controllers/messages_controller.rb
MessagesController < BaseController
  def update
    # Some business logic

    return redirect_to:index, notice: t(".notice") if @resource.save
    render :edit, alert: t(".alert")
  end
 end
config/locales/model.ja.yml

ja:
  messages:
    update:
      notice: "メッセージの更新に成功しました。"
      alert: "メッセージの更新ができません。"

6. 設定した日本を表示させる

app/views/users/index.html.erb

#### viewで使う場合

# 対応するビューの中ではツリーを省略できる。
# 「ユーザ一覧」が表示される(users.index.titleを参照。)
# 「users.index.html.erb」に記述されているので
# ja.ymlの
# users:
    index:
部分を自動的に読み込んでいるから省略できる
<%= t '.title' %>

# 省略しない記述は以下のようになる。
# 「ユーザ一覧」が表示される
<%= t 'users.index.title' %>


# 日付・時刻(DateオブジェクトやTimeオブジェクト)変換するする場合
<%= l Time.now %>

# 翻訳対象がnilになる(変数とか)場合、defaultを設定しておく
<%= l nil %>
=> I18n::ArgumentError: Object must be a Date, DateTime or Time object. nil given.
↓
↓ nil対策
↓
<%= l nil, default: '' %>
=> ""

# t = translate (config/locales配下の訳文(ja.yml)を参照する)(I18n.t)
# l = localize (DateオブジェクトやTimeオブジェクトを現地のフォーマットに変換する)(I18n.l)

-----------------------------------------------

#### modelで使う場合

# 「ユーザー」が表示される。(activerecord.models.userを参照)
<li><%= User.model_name.human %></li>

# 「ID」が表示される。(activerecord.attributes.user.idを参照)
<li><%= User.human_attribute_name(:id) %></li>

# 「メールアドレス」が表示される。(activerecord.attributes.user.emailを参照)
<li><%= User.human_attribute_name(:email) %></li>

7. 様々な記述方法 (加えてYAMLの記法を学ぶと良い)

ja.yml
# サンプルコード
# 全ファイル共通の使いまわす可能性のあるワードは dictionary を起点にする。
ja:
  dictionary:
    messages:
      # 呼び出し方
      # ■ 階層を.でつなげた文字列を渡す
      #   I18n.t("dictionary.messages.hello_user", user_name: "ヤマダ")で呼び出す
      # ■ scopeで階層を区切り(文字列でも配列でも可能)、対象のkeyを渡す
      #   I18n.t(:hello_user, scope: "dictionary.messages", user_name: "ヤマダ")
      #   I18n.t(:hello_user, scope: [:dictionary, :messages], user_name: "ヤマダ")
      hello_user: 'こんにちは%{user_name}さん'  
    words:
      user: &user 'ユーザ情報'          # &user はエイリアスで *user で参照できる。(同一ファイル内のみ有効)
      user_copy: *user                # 'ユーザ情報' となる。

helpers

ja.yml
# サンプルコード
# ヘルパー関数はhelpersを起点にする。
# ヘルパー関数内で tメソッドを使用すると、呼び出し元のビューに基づいたパスが呼び出される。
# 例えばusersのshowから呼ばれたヘルパー関数内で t('.hoge') を実行した場合 users.show.hoge が参照される。
# 呼び出し元によって文言を変えたい場合はビュー側に記述する。(そんなことあるかわからないけど。)

ja:
  helpers:
    submit:
      create: "%{model}を登録する"
      update: "%{model}を更新する"
      account:
        create: "保存する"
      login_form:
        create: "ログインする"
    select:
      prompt: "選択してください。"
    links:
      show: "詳細"
      back: "戻る"
      destroy: "削除"
      edit: "編集"
      new: "新規作成"
      cancel: "キャンセル"

errors

ja.yml
# サンプルコード
# グローバルな感じのエラーはerrorsを起点にする。
ja:
  errors:
    format: "%{message}"
    messages: &errors_messages
      inclusion: "%{attribute}が正しくありません。"
      exclusion: "%{attribute}は正しくありません。"
      invalid: "%{attribute}が正しくありません。"
      confirmation: "%{attribute}が一致しません。"
      accepted: "%{attribute}をチェックしてください。"
      empty: "%{attribute}を入力してください。"
      blank: "%{attribute}を入力してください。"
      required: "%{attribute}を入力してください。"
      too_long: "%{attribute}は%{count}文字以内で入力してください。"
      too_short: "%{attribute}は%{count}文字以上で入力してください。"
      wrong_length: "%{attribute}は%{count}文字で入力してください。"
      not_a_number: "%{attribute}は数値で入力してください。"
      not_an_integer: "%{attribute}は整数で入力してください。"
      greater_than: "%{attribute}は%{count}より大きい値を入力してください。"
      greater_than_or_equal_to: "%{attribute}は%{count}以上の値を入力してください。"
      equal_to: "%{attribute}は%{count}にしてください"
      less_than: "%{attribute}は%{count}より小さい値にしてください"
      less_than_or_equal_to: "%{attribute}は%{count}以下の値にしてください"
      odd: "%{attribute}は奇数を入力してください。"
      even: "%{attribute}は偶数を入力してください。"
      taken: "%{attribute}がすでに使用されています。"
      before: "%{attribute}は%{threshold}よりも前の%{type}を入力してください。"
      before_or_equal_to: "%{attribute}は%{threshold}以前の%{type}を入力してください。"
      after: "%{attribute}は%{threshold}よりも後の%{type}を入力してください。"
      after_or_equal_to: "%{attribute}は%{threshold}以降の%{type}を入力してください。"
      out_of_contract: "%{attribute}が契約期間外です。"
      file_size_exceed: "%{file_name}のファイルサイズが大きすぎます。"
      file_type_invalid: "アップロードできないファイル形式です。"
      file_name_too_long: "ファイル名が長すぎます、%{count}文字以内の名称のファイルを指定してください。"
      access_forbidden: "指定された情報にアクセスする権限がありません。"
      pixel_size_too_large: "画像ファイルの大きさは %{width} × %{height} ピクセル以内にしてください。"
      invalid_chars: "%{attribute}に使用できない文字が含まれています(%{chars})。"
      in_between: "の容量は%{min}以上%{max}以下にしてください。"
      spoofed_media_type: "%{attribute}の拡張子と内容が一致していません。"

flash

ja.yml
# サンプルコード
# flashのi18nの設定方法

ja:
  flash:
    new: 作成しました!
    updated: 更新しました!
    failed: 失敗しました!
    destroy: 削除しました!
    login: ログインしました!
    logout: ログアウトしました!
    limited_access: 不正な侵入です!!
    tag_follow: タグをフォローしました!
    tag_not_follow: タグをフォローできませんでした。
    tag_remove_follow: タグのフォローを解除しました!

複数行テキスト

config/locales/user_mailer/ja.yml
# サンプルコード
# 参考URL: https://qiita.com/jerrywdlee/items/d5d31c10617ec7342d56 (YAMLで複数行テキストを書きたい時のあれこれ)
# 基本形に「-」を追加することで最終行の改行を削除します。
ja:
  user_mailer:
    signature: |-
      株式会社Hoge
      営業時間:10:00-19:00
      営業日:月~金(土日祝除く)

# 基本形
ja:
  foo: |
    bar
    baz

Placeholders

<%= f.input :name, placeholder: true %>
ja:
  helpers:
    placeholder:
      message:
        name: "プレースホルダー"

HTMLを使う

_htmlで終わるkeyは、HTMLとして表示されます。

名前が'html'であるキー、および名前が'_html'で終わるキーは、「HTML safe」とマークされます。これらのキーをビューで使うと、その部分のHTMLはエスケープされません。

app/views/dashboard/show.html.erb
# app/views/dashboard/show.html.erb
<%= t ".welcome_html", name: "山田" %>
config/locales/views/dashboard/ja.yml
ja:
  dashboard:
    show:
      hello_html: "Welcome, <strong>%{name}</strong>"

メールの件名

引数なし

user_mailer.rb
# user_mailer.rb 
class UserMailer < ActionMailer::Base
  def welcome(user)
    #...
  end
end
ja:
  user_mailer:
    welcome:
      subject: "ようこそ!"

引数あり

default_i18n_subjectを使う

user_mailer.rb
# user_mailer.rb
class UserMailer < ActionMailer::Base
  def welcome(user)
    mail(to: user.email, subject: default_i18n_subject(user: user.name))
  end
end
ja:
  user_mailer:
    welcome:
      subject: "%{user}さん, ようこそ!"

参考

Rails国際化 (I18n) API
Ruby on RailsのI18nで使用する名前空間に関してのまとめと、ベストプラクティスの検討。 - 波打際のブログさん

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
415
Help us understand the problem. What are the problem?