Devise 日本語化
現時点では devise に関するエラーメッセージはデフォルトの英語のままとなっています。
例えば、空欄を許可しないカラムについては "can't be blank" と表示されます。
(前回、User.nickname のテストを書いた時にはそれを利用していました)
しかし TechLog は日本語の Web アプリですので
devise 関連で表示されるエラーメッセージは日本語化することで、
ユーザーがより使いやすいアプリケーションにしていきます。
テスト修正
今回もテスト駆動開発の流れに則り、先にあるべき姿(仕様)をテストで定義しましょう。
日本語化対応を行った結果、各種エラーメッセージは日本語が表示されることを期待したテストに修正します。
devise 関連のエラーメッセージのチェックはモデルの Spec・System Spec の両方で行なっていたため、それぞれ修正しましょう。
spec/models/user_spec.rb
require 'rails_helper'
describe User do
let(:nickname) { 'テスト太郎' }
let(:email) { 'test@example.com' }
let(:user) { User.new(nickname: nickname, email: email, password: password, password_confirmation: password) } # 変数に格納
describe '.first' do
before do
create(:user, nickname: nickname, email: email)
end
subject { described_class.first }
it '事前に作成した通りのUserを返す' do
expect(subject.nickname).to eq('テスト太郎')
expect(subject.email).to eq('test@example.com')
end
end
describe 'validation' do
let(:password) { '12345678' }
describe 'nickname属性' do
describe '文字数制限の検証' do
context 'nicknameが20文字以下の場合' do
let(:nickname) { 'あいうえおかきくけこさしすせそたちつてと' } # 20文字
it 'User オブジェクトは有効である' do
expect(user.valid?).to be(true)
end
end
context 'nicknameが20文字を超える場合' do
let(:nickname) { 'あいうえおかきくけこさしすせそたちつてとな' } # 21文字
it 'User オブジェクトは無効である' do
user.valid?
expect(user.valid?).to be(false)
expect(user.errors[:nickname]).to include('は20文字以下に設定して下さい。')
end
end
end
describe '存在性の検証' do
context 'nicknameが空欄の場合' do
let(:nickname) { '' }
it 'User オブジェクトは無効である' do
expect(user.valid?).to be(false)
expect(user.errors[:nickname]).to include("が入力されていません。")
end
end
end
end
end
end
spec/system/users_spec.rb
require 'rails_helper'
describe 'User', type: :system do
before { driven_by :selenium_chrome_headless }
# ユーザー情報入力用の変数
let(:email) { 'test@example.com' }
let(:nickname) { 'テスト太郎' }
let(:password) { 'password' }
let(:password_confirmation) { password }
describe 'ユーザー登録機能の検証' do
before { visit '/users/sign_up' }
# ユーザー登録を行う一連の操作を subject にまとめる
subject do
fill_in 'user_nickname', with: nickname
fill_in 'user_email', with: email
fill_in 'user_password', with: password
fill_in 'user_password_confirmation', with: password_confirmation
click_button 'ユーザー登録'
end
context '正常系' do
it 'ユーザーを作成できる' do
expect { subject }.to change(User, :count).by(1) # Userが1つ増える
expect(page).to have_content('ユーザー登録に成功しました。')
expect(current_path).to eq('/') # ユーザー登録後はトップページにリダイレクト
end
end
context '異常系' do
context 'エラー理由が1件の場合' do
let(:nickname) { '' }
it 'ユーザー作成に失敗した旨のエラーメッセージを表示する' do
subject
expect(page).to have_content('エラーが発生したためユーザーは保存されませんでした。')
end
end
context 'エラー理由が2件以上の場合' do
let(:nickname) { '' }
let(:email) { '' }
it '問題件数とともに、ユーザー作成に失敗した旨のエラーメッセージを表示する' do
subject
expect(page).to have_content('エラーが発生したためユーザーは保存されませんでした。')
end
end
context 'nicknameが空の場合' do
let(:nickname) { '' }
it 'ユーザーを作成せず、エラーメッセージを表示する' do
expect { subject }.not_to change(User, :count) # Userが増えない
expect(page).to have_content('ニックネーム が入力されていません。') # エラーメッセージのチェック
end
end
context 'nicknameが20文字を超える場合' do
let(:nickname) { 'あ' * 21 }
it 'ユーザーを作成せず、エラーメッセージを表示する' do
expect { subject }.not_to change(User, :count)
expect(page).to have_content('ニックネーム は20文字以下に設定して下さい。')
end
end
context 'emailが空の場合' do
let(:email) { '' }
it 'ユーザーを作成せず、エラーメッセージを表示する' do
expect { subject }.not_to change(User, :count)
expect(page).to have_content('メールアドレス が入力されていません。')
end
end
context 'passwordが空の場合' do
let(:password) { '' }
it 'ユーザーを作成せず、エラーメッセージを表示する' do
expect { subject }.not_to change(User, :count)
expect(page).to have_content('パスワード が入力されていません。')
end
end
context 'passwordが6文字未満の場合' do
let(:password) { 'a' * 5 }
it 'ユーザーを作成せず、エラーメッセージを表示する' do
expect { subject }.not_to change(User, :count)
expect(page).to have_content('パスワード は6文字以上に設定して下さい。')
end
end
context 'passwordが128文字を超える場合' do
let(:password) { 'a' * 129 }
it 'ユーザーを作成せず、エラーメッセージを表示する' do
expect { subject }.not_to change(User, :count)
expect(page).to have_content('パスワード は128文字以下に設定して下さい。')
end
end
context 'passwordとpassword_confirmationが一致しない場合' do
let(:password_confirmation) { "#{password}hoge" } # passwordに"hoge"を足した文字列にする
it 'ユーザーを作成せず、エラーメッセージを表示する' do
expect { subject }.not_to change(User, :count)
expect(page).to have_content('確認用パスワード が一致していません。')
end
end
end
end
describe 'ログイン機能の検証' do
before do
create(:user, nickname: nickname, email: email, password: password, password_confirmation: password) # ユーザー作成
visit '/users/sign_in'
fill_in 'user_email', with: email
fill_in 'user_password', with: 'password'
click_button 'ログイン'
end
context '正常系' do
it 'ログインに成功し、トップページにリダイレクトする' do
expect(current_path).to eq('/')
end
it 'ログイン成功時のフラッシュメッセージを表示する' do
expect(page).to have_content('ログインしました。')
end
end
context '異常系' do
let(:password) { 'NGpassword' }
it 'ログインに失敗し、ページ遷移しない' do
expect(current_path).to eq('/users/sign_in')
end
it 'ログイン失敗時のフラッシュメッセージを表示する' do
expect(page).to have_content('メールアドレスまたはパスワードが違います。')
end
end
end
describe 'ログアウト機能の検証' do
before do
user = create(:user, nickname: nickname, email: email, password: password, password_confirmation: password) # ユーザー作成
sign_in user # 作成したユーザーでログイン
visit '/'
click_button 'ログアウト'
end
it 'トップページにリダイレクトする' do
expect(current_path).to eq('/')
end
it 'ログアウト時のフラッシュメッセージを表示する' do
expect(page).to have_content('ログアウトしました。')
end
end
end
また、前のカリキュラムでナビゲーションバーを追加したことでログアウトの操作を出来るようになったため、
ログアウト時のメッセージを検証するテストも追加してあります。
現時点ではもちろんテストが失敗することを確認してください。
$ bin/rspec spec/system/users_spec.rb
...
Finished in 32.33 seconds (files took 0.45752 seconds to load)
16 examples, 12 failures
次は、これらのテストを通すために各種設定をしていきましょう。
アプリ全体の日本語化設定
Rails の言語は config/application.rb
で設定します。
デフォルトでは英語ですが、次のように 1 行を追加することで日本語として設定されます。
...
module TechLog
class Application < Rails::Application
...
config.i18n.default_locale = :ja # 追記
end
end
これにより devise も日本語のエラーメッセージを表示しようとするのですが、
devise の gem 自体には日本語のエラーメッセージが含まれていません。
翻訳ファイルを用意
次に、英語と日本語の対訳を .yml 形式のファイルで置いてあげます。
config/locales
というディレクトリの直下にdevise.views.ja.yml
というファイルを作成し、
中身を次のように編集します。
config/locales/devise.views.ja.yml ※新規作成
ja:
activerecord:
errors:
models:
user:
attributes:
email:
taken: "は既に使用されています。"
blank: "が入力されていません。"
invalid: "は有効でありません。"
nickname:
blank: "が入力されていません。"
too_long: "は%{count}文字以下に設定して下さい。"
password:
blank: "が入力されていません。"
too_short: "は%{count}文字以上に設定して下さい。"
too_long: "は%{count}文字以下に設定して下さい。"
invalid: "は有効でありません。"
password_confirmation:
confirmation: "が一致していません。"
attributes:
user:
nickname: "ニックネーム"
email: "メールアドレス"
password: "パスワード"
password_confirmation: "確認用パスワード"
models:
user: "ユーザー"
errors:
messages:
not_saved: "エラーが発生したため%{resource}は保存されませんでした。"
devise:
failure:
invalid: "%{authentication_keys}またはパスワードが違います。"
registrations:
user:
signed_up: ユーザー登録に成功しました。
sessions:
new:
sign_in: ログイン
signed_in: ログインしました。
user:
signed_out: ログアウトしました。
このように .yml 形式で、エラー内容を階層に分けて日本語訳を記載するというのは、devise のお作法に則ったやり方となります。
現在はユーザー登録・ログイン・ログアウト機能しか実装していませんが、パスワード再設定など devise 特有の機能を利用するときには本ファイルを編集することも覚えておきましょう。
テストを実行
さて、ブラウザで動作確認する前にテストを実行してみましょう。
実はこの時点でコケるテストが一つあります。
$ bin/rspec
...
Failures:
1) Home ナビゲーションバーの検証 ログインしている場合 ログアウトリンクが機能する
Failure/Error: expect(page).not_to have_content('ログアウト')
expected not to find text "ログアウト" in "TechLog\nユーザー登録\nログイン\nログアウトしました。\nHome#top\nFind me in app/views/home/top.html.erb"
...
Failed examples:
rspec ./spec/system/home_spec.rb:52 # Home ナビゲーションバーの検証 ログインしている場合 ログアウトリンクが機能する
ナビゲーションバーを追加した時のテストがコケてしまいました。
テストのエラーメッセージをチェックしてみます。
Failure/Error: expect(page).not_to have_content('ログアウト')
expected not to find text "ログアウト" in "TechLog\nユーザー登録\nログイン\nログアウトしました。\nHome#top\nFind me in app/views/home/top.html.erb"
expected not to find text "ログアウト"...
という一文に注目してください。
「ログアウトというテキストが含まれないことを期待していたが、実際には含まれていた」というエラーです。
ここでテストの中身を確認してみます。
spec/system/home_spec.rb
...
it 'ログアウトリンクが機能する' do
click_button 'ログアウト'
# ログインしていない状態のリンク表示パターンになることを確認
expect(page).to have_link('ユーザー登録', href: '/users/sign_up')
expect(page).to have_link('ログイン', href: '/users/sign_in')
expect(page).not_to have_content('ログアウト')****
end
...
上記のうち expect(page).not_to have_content('ログアウト')
という箇所になります。
これは、ログアウトのリンク有無の識別として「ログアウトという文字があるかどうか」を基準にしていました。
しかし今回、ログアウト成功時のフラッシュメッセージを「ログアウトしました。」というテキストに設定したため、この中の「ログアウト」をログアウトリンクとして認識されています。
原因はテストの書き方にあることが分かりましたので、問題のあったテスト 1 行を修正しましょう。
幸い、ログアウトリンクは type が submit
でしたので、Capybara の have_button
マッチャを使用します。
これを用い、次のように変更します。
spec/system/home_spec.rb
...
it 'ログアウトリンクが機能する' do
click_button 'ログアウト'
# ログインしていない状態のリンク表示パターンになることを確認
expect(page).to have_link('ユーザー登録', href: '/users/sign_up')
expect(page).to have_link('ログイン', href: '/users/sign_in')
expect(page).not_to have_button('ログアウト') # 修正
end
...
修正後、先ほどコケていたテストも含めすべてのテストに通ることを確認しましょう。
$ bin/rspec
...
Finished in 9.45 seconds (files took 0.41715 seconds to load)
29 examples, 0 failures
最後に、実際に画面上ではどのように変わっているかをブラウザで確認していきます。
テストで通っていれば機能要件は満たせているものの、見た目として違和感がないかをチェックすることも重要です。
動作確認
bin/dev
コマンドで開発用サーバを起動し、確認します。
(config/application.rb
に変更を行った場合は開発用サーバの再起動が必要になるため、前回から起動したままだった場合は再起動してください)
ユーザー登録、ログイン、ログアウトそれぞれで出るメッセージがすべて日本語になっていることを確認しましょう。
ユーザー登録
http://localhost:3000/users/sign_up
にアクセスしましょう。
最初はすべての欄を空にしたまま、ユーザー登録に失敗します。
意図したメッセージが表示されています。
次は、ちゃんとした情報を埋めてユーザー登録に成功しましょう。
成功時のメッセージがバッチリ表示されていますね。
ログイン
http://localhost:3000/users/sign_in
にアクセスしましょう。
最初は適当な認証情報でログインに失敗します。
ログイン失敗時
認証に失敗した理由とともに、エラーメッセージが出ていますね。
次は作成したユーザーの認証情報でログインします。
ログイン成功時
「ログインしました。」と表示されていますね。
ログアウト
最後に、ログアウト時のメッセージが翻訳できていることを確認しましょう。
ナビゲーションバーからログアウトリンクを選択します。
「ログアウトしました。」と表示されていれば OK です。
変更をコミット
ここまでの変更をコミットしておきます。
$ git add .
$ git commit -m "devise関連のメッセージを日本語化"
$ git push
これで devise ユーザー認証周りの変更は一旦おしまいです。
ボリュームが多く、devise のルールを覚えるのは大変だったかと思います。
お疲れ様でした!
宿題
devise-i18n
devise-i18n
というまた別な gem を使うと、翻訳ファイルを自分で用意しなくてもコマンドで生成したりもできます。
(ただし、不要な翻訳の取捨選択が必要なことや、ブラックボックス化を避けるため今回は使いませんでした)
gem の使い方を調べる前提とはなりますが、興味がある方は参考にしてみてください。
have_button
今回、Capybara でログアウトリンク(ボタン)を探すために have_button
というマッチャを使いました。
have_button
マッチャがどういう要素を見つけてくるのかは、前にも紹介した人気の Qiita 記事で復習しておきましょう。