I18n、使ってますか?
多言語化しなくても、アプリケーション内で文言を統一するなどの用途で使用している方も多いのではないかと思います。
一見、YAMLを書いて、viewやcontrollerで参照するぐらいの単純な機能に見えますが、実はかなりたくさんの便利機能があるので、まとめてみました。
日付や時刻のフォーマットを定義する
普段よく使うのはI18n#t
だけど、日付や時刻をローカライズするI18n#l
もある。
ja:
date:
formats:
default: "%Y/%m/%d"
long: "%Y年%m月%d日(%a)"
short: "%m/%d"
time:
formats:
default: "%Y/%m/%d %H:%M:%S"
long: "%Y年%m月%d日(%a) %H時%M分%S秒 %z"
short: "%y/%m/%d %H:%M"
> I18n.l(Date.today)
=> "2017/06/25" # 指定がない場合はdefaultを使う
> I18n.l(Date.today, format: :short)
=> "06/25"
> I18n.l(Time.now)
=> "2017/06/25 15:57:25"
> I18n.l(Time.now, format: :long)
=> "2017年06月25日(日) 15時57分46秒 +0900"
Rubyスクリプトで訳文を定義する
訳文は通常YAMLで定義するけど、実はRubyスクリプトで定義することもできる。
{
ja: {
hello: "こんにちは"
}
}
> I18n.t(:hello)
=> "こんにちは"
Rubyなので、スクリプトで定義を生成できる。
できるけど、いい活用方法が思いつかない。。
{
ja: (1..101).map{|i| ["dog#{i}", "#{i}匹わんちゃん"]}.to_h
}
> I18n.t(:dog1)
=> "1匹わんちゃん"
> I18n.t(:dog101)
=> "101匹わんちゃん"
訳文としてlambdaを与えることもできる。
できるけど、訳文にコードを入れるのはI18nの守備範囲を逸脱している気がする。。
{
ja: {
date: {
formats: {
forever5: ->(date, prms) {
# 例なので投げやりな計算
today = Date.today
month_diff = (today.year*12 + today.month) - (date.year*12 + date.month) - 60
"#{prms[:name]}さんは、5歳#{month_diff}ヶ月です"
}
}
}
}
}
> I18n.l(Date.new(1973, 4, 2), name: "ガチャピン", format: :forever5)
=> "ガチャピンさんは、5歳470ヶ月です"
I18nの訳文を動的に追加する
I18n::Backend::Base#store_translations
を使う。
> I18n.backend.store_translations :ja, hello: 'こんにちは'
> I18n.t(:hello)
=> "こんにちは"
ロケールごとにViewのテンプレートを切り替える
ヘルプページなど、内容の大部分が文章であるときなどに便利。
例えば、HelpController
のindex
アクションを呼ぶ場合、通常はapp/views/helps/index.html.erb
が使われるが、index.ja.html.erb
を置いておくと、ロケールが:ja
のときにこちらを使ってくれる。
ネストされた訳文の階層をシンボルで指定する
ja:
foo:
bar:
test: "テスト"
この場合、以下のように、階層を.
でつなげた文字列で指定するのが一般的。
> I18n.t("foo.bar.test")
=> "テスト"
:scope
を使うことで、キーと階層を分けて指定することもできる。
> I18n.t("bar.test", scope: :foo)
=> "テスト"
> I18n.t(:test, scope: "foo.bar")
=> "テスト"
> I18n.t(:test, scope: [:foo, :bar])
=> "テスト"
階層を動的に変える場合は、文字列の式展開でやるよりキレイになるかも。
翻訳時に変数を渡す
ja:
dogs: "%{num}匹わんちゃん"
> I18n.t(:dogs, num: 101)
=> "101匹わんちゃん"
訳文が見つからない場合に出す文字列を指定する
:default
オプションを指定すると、訳文が見つからない場合にそちらを見てくれる。
どんなユースケースがあるんだろう。。
> I18n.t('undefined.key')
=> "translation missing: ja.undefined.key"
# 文字列を渡すと、それをそのまま出す
> I18n.t('undefined.key', default: "undefined!")
=> "undefined!"
# シンボルを渡すと、それをキーとして訳文を探す
> I18n.t('undefined.key', default: :"foo.bar.test")
=> "テスト"
# 配列を渡すと、先頭から順に見ていく
> I18n.t('undefined.key', default: [:"foo.bar.test", "undefined!"])
=> "テスト"
ロケールを明示的に与える
:locale
オプションでロケールを明示的に与えることができる。
あまりユースケースが思いつかない。テストのときとか…?
ja:
hello: こんにちは
en:
hello: Hello
> I18n.t(:hello)
=> ”こんにちは”
> I18n.t(:hello, locale: :en)
=> "Hello"
訳文にHTMLを使用する
キー名がhtml
、もしくは_html
で終わっていれば、html_safeとみなされ、viewで使う場合にエスケープされない。ただし、式展開%{}
の中身はエスケープされる。
ja:
hello_str: "<b>こんにちは!</b>"
hello_html: "<b>こんにちは!</b>"
hello:
html: "<b>こんにちは!</b>"
hello_with_name:
html: "<b>こんにちは!%{name}さん</b>"
<div><%= t('hello_str') %></div>
<div><%= raw t('hello_str') %></div>
<div><%= t('hello_html') %></div>
<div><%= t('hello.html') %></div>
<div><%= t('hello_with_name.html', name: "ガチャピン") %></div>
<div><%= t('hello_with_name.html', name: "<span>ガチャピン</span>") %></div>
訳文を一括取得する
キーを配列で渡すことによって、訳文を一括取得可能。
CSVのヘッダを取ってくるときとかに便利かも?
ja:
csv:
header:
user:
customer_id: 顧客番号
name: 名前
address: 住所
> I18n.t([:customer_id, :name, :address], scope: [:csv, :header, :user])
=> ["顧客番号", "名前", "住所"]
特定のViewファイル専用の訳文を簡単に参照する
Viewファイルのパスに沿った階層で訳文を定義することで、Viewファイルの中で簡単に参照できる。
ja:
users:
index:
title: 顧客一覧
form:
same_address: 顧客住所と同じ
<%= t(".title") %>
<%# => "顧客一覧” %>
<%= t(".same_address") %>
<%# => "顧客住所と同じ” %>
モデル名やモデルのアトリビュート名を取得する
指定された階層に置くことで、Model.model_name.human
でモデル名を、Model.human_attribute_name(attribute)
でアトリビュート名を取得できる。
ja:
activerecord:
# モデル名
models:
user: 顧客
# モデルごとのアトリビュート名
attributes:
user:
name: 名前
address: 住所
# 全モデル共通のアトリビュート名
attributes:
created_at: 作成日時
updated_at: 更新日時
> User.model_name.human
=> "顧客"
> User.human_attribute_name(:name)
=> "名前"
> User.human_attribute_name(:created_at)
=> "作成日時"
与えた数によって単数形/複数形を切り替える
count
というパラメーターを渡すことで、数に応じて単数形/複数形を切り替えることができる。
ja:
family:
one: "独身"
other: "%{count}人家族"
> I18n.t(:family, count: 1)
=> "独身"
> I18n.t(:family, count: 101)
=> "101人家族"
Model.model_name.human
やModel.human_attribute_name(attribute)
もほぼ同様だけど、count
を与えなかった場合はone
が使われる。
ja:
activerecord:
models:
anpan:
one: アンパンマン
other: アンパンメン
> Anpan.model_name.human
=> "アンパンマン"
> Anpan.model_name.human(count: 1)
=> "アンパンマン"
> Anpan.model_name.human(count: 100)
=> "アンパンメン"
> Anpan.model_name.human(count: Anpan.count)
=> "アンパンメン"
one
other
だけでなく、定義次第でいろいろ対応可能。
参考:https://github.com/svenfuchs/i18n/blob/master/test/test_data/locales/plurals.rb
ActiveRecordのバリデーションメッセージを定義する
バリデーションに引っかかった場合、以下の優先順位でメッセージが検索される。
activerecord.errors.models.[model_name].attributes.[attribute_name].[message_key]
activerecord.errors.models.[model_name].[message_key]
activerecord.errors.messages.[message_key]
errors.attributes.[attribute_name].[message_key]
errors.messages.[message_key]
例えば、以下のようなバリデーションが設定されている場合、
class User < ActiveRecord::Base
validates :name, presence: true
end
presence
に対応するメッセージのキー名はblank
なので、name
がバリデーションに引っかかると以下の順にエラーメッセージが検索される。
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank
また、一部のバリデーションは、オプションとしてそのバリデーションに関連する数値が渡ってくるので、それを組み込むこともできる。
バリデーションに対するメッセージキーとオプションはこちらを参照。
Action Mailerメールの件名を訳文を定義する
mailメソッドに件名が渡されなかった場合、デフォルトで[mailer_scope].[action_name].subject
というキーで件名を検索する。
ja:
user_mailer:
welcome:
subject: ご登録ありがとうございます。
その他のI18n関連メソッド
ActionView
distance_of_time_in_words
投稿から大体◯日とか、表示するアレ。
http://api.rubyonrails.org/v5.1/classes/ActionView/Helpers/DateHelper.html#method-i-distance_of_time_in_words
こんな感じで定義する。
datetime_select / select_month
- datetime_select
- select_month
月の表示名は、"date.month_names"で定義する。
また、"date.order"で年月日の表示順も切替可能。
ActionView::Helpers::NumberHelper
- number_to_currency
- 数値を通貨フォーマットに変換する
- number_with_precision
- 数値を特定の桁で丸める
- number_to_percentage
- 数値をパーセントに変換する
- number_with_delimiter
- 数値に桁区切り文字を追加する
- number_to_human_size
- バイト単位の数値を人間に読みやすい桁に変換する
これらのメソッドの一部のオプションはI18nの定義ファイルに設定することで、ロケールごとに変えることが可能。
jp:
number:
currency:
format:
unit: "円"
format: "%n%u"
negative_format: "-%n%u"
precision: 0
<%= number_to_currency(123456789) %>
<%# => 123,456,789円 %>
ActiveSupport
Array#to_sentence
複数の要素をつなげて文章っぽくしてくれる。
ja:
support:
array:
words_connector: "、"
two_words_connector: "と"
last_word_connector: "、そして"
> %w(地震 雷 火事 親父).to_sentence
=> "地震、雷、火事、そして親父"
> %w(ぐり ぐら).to_sentence
=> "ぐりとぐら"
参考