mcpField
I
i18n

i18n

github.com/thinkoodle開発7190 回インストール8 週間前に更新
リポジトリ

i18n について

Railsアプリに国際化とローカライズを導入する開発者向けのI18nスキルです。ロケールファイルの整理、リクエストごとの言語選択、日付・数値・モデル・メールのローカライズを扱います。

主な機能

  1. 01ロケールファイルで翻訳を管理
  2. 02利用可能なロケールとフォールバックの設定
  3. 03遅延検索と補間の利用
  4. 04日付、数値、複数形のローカライズ
  5. 05モデル、検証エラー、メールの翻訳
  6. 06リクエストごとの安全な言語切り替え

活用シーン

  1. 01既存のRailsアプリに多言語対応を追加
  2. 02ロケールファイルの整理と分割
  3. 03ユーザー設定やURLによる言語選択
  4. 04フォームエラー、日付、メールのローカライズ

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/thinkoodle/rails-skills --skill i18n

インストール後、Claude Code で/i18nを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
locale-files.md
locale-switching.md
lookups.md
model-translations.md
pluralization.md
testing.md

詳細

Rails I18n エキスパート

I18n フレームワークを使って、Rails アプリケーションを国際化およびローカライズします。ユーザーに表示される文字列はすべてロケールファイルに記述し、ハードコードしないでください。

基本方針

基本原則:

  1. すべての文字列をロケールファイルに記述する — ビュー、コントローラー、メーラー、モデルにユーザー向けテキストをハードコードしないでください
  2. あらゆる場所で遅延検索を使う — ビューやコントローラーでは、books.index.title ではなく .title を使います
  3. 言語ではなく機能ごとに整理する — ロケールファイルは、1つの巨大なファイルにまとめず、ドメイン(モデル、ビュー、デフォルトなど)ごとに分割します
  4. YAML を基本にする — 日付形式に Ruby のラムダが必要な場合を除き、.yml ファイルを使います
  5. 開発/テスト環境ではエラーを明示する — raise_on_missing_translations = true を設定し、キーの不足を早期に検出します

このスキルを使う場面

  • 既存の Rails アプリに I18n サポートを追加するとき
  • YAML ロケールファイルを作成または編集するとき
  • t()/I18n.t() や l()/I18n.l() ヘルパーを使うとき
  • ロケール切り替え(URL、サブドメイン、ヘッダー、ユーザー設定)を設定するとき
  • Active Record のモデル名、属性、エラーメッセージを翻訳するとき
  • 日付、時刻、数値、通貨をローカライズするとき
  • 英語以外のロケールの複数形ルールを設定するとき
  • 大規模なアプリケーションでロケールファイルを整理するとき
  • フォールバックや利用可能なロケールを設定するとき

手順

ステップ 1:既存の I18n 設定を確認する

まず、プロジェクトの現在の I18n 設定を確認してください — 慣習の不一致はキー検索の失敗につながります。

# Check existing locale files
find config/locales -name "*.yml" -o -name "*.rb" | sort

# Check I18n config
grep -r "i18n" config/application.rb config/initializers/ config/environments/

# Check available locales
grep -r "available_locales" config/

# Check for existing translation usage
rg "I18n\.t\b|\ t[\(\ ][\'\"\.]" --type ruby --type erb -l

# Check for hardcoded strings in views (potential I18n candidates)
rg -l ">[A-Z][a-z]+" app/views/ --type erb

既存の慣習に合わせてください。プロジェクトでフラットなキーを使っている場合は、ネストしたキーを導入しないでください。機能ごとに整理している場合は、それに従ってください。

ステップ 2:I18n を適切に設定する

config/application.rb の最小限の設定:

# config/application.rb
config.i18n.available_locales = [:en, :es, :fr]
config.i18n.default_locale = :en
config.i18n.fallbacks = true  # Falls back to default_locale
config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")]

テスト/開発環境では、翻訳の不足を検出します:

# config/environments/test.rb
config.i18n.raise_on_missing_translations = true

# config/environments/development.rb
config.i18n.raise_on_missing_translations = true

available_locales を設定してください — 設定しないと、任意のロケール文字列が受け入れられ、ファイルシステムのトラバーサル攻撃や予期しないフォールバック動作につながる可能性があります。

ステップ 3:翻訳ヘルパーを正しく使う
基本的な検索
# In views (translate helper auto-available)
t("hello")                          # Simple key
t("messages.welcome")               # Nested key
t(:welcome, scope: :messages)       # Same thing, symbol + scope

# In controllers/models/services
I18n.t("messages.welcome")

# With default fallback
t("missing.key", default: "Fallback text")
t("missing.key", default: [:other_key, "Final fallback"])
遅延検索(こちらを優先)

遅延検索は、ビューのパスまたはコントローラーのアクションに基づいて自動的にスコープを設定します。

# config/locales/en.yml
en:
  books:
    index:
      title: "All Books"
      empty: "No books found"
    show:
      title: "Book Details"
    create:
      success: "Book created!"
      failure: "Could not create book."
<%# app/views/books/index.html.erb %>
<h1><%= t(".title") %></h1>         <%# Resolves to books.index.title %>
<p><%= t(".empty") %></p>           <%# Resolves to books.index.empty %>
# app/controllers/books_controller.rb
class BooksController < ApplicationController
  def create
    if @book.save
      redirect_to @book, notice: t(".success")   # books.create.success
    else
      flash.now[:alert] = t(".failure")           # books.create.failure
      render :new, status: :unprocessable_entity
    end
  end
end

ビューやコントローラーでは、遅延検索(.key)を優先してください — 翻訳キーを DRY に保ち、ファイル構造に対応させられます。共有キーやグローバルキーを参照する場合に限り、完全なパスを使います。

文字列の補間
en:
  greeting: "Hello, %{name}!"
  item_count: "You have %{count} items in %{location}"
t("greeting", name: current_user.name)
t("item_count", count: 5, location: "your cart")

scope や default を補間変数名に使わないでください。これらは I18n で予約されており、I18n::ReservedInterpolationKey が発生します。

複数形
en:
  notifications:
    zero: "No notifications"      # optional for English
    one: "1 notification"
    other: "%{count} notifications"
t("notifications", count: 0)   # => "No notifications"
t("notifications", count: 1)   # => "1 notification"
t("notifications", count: 42)  # => "42 notifications"

:count 変数は特別な意味を持ちます — 複数形を選択し、さらに文字列への補間も行います。

英語で必要なのは one と other だけです。ほかの言語では異なる形式が必要です。

  • アラビア語: zero、one、two、few、many、other
  • ロシア語: one、few、many、other
  • 日本語: other のみ

ロケール固有の複数形ルールには、rails-i18n gem を使います。

HTML セーフな翻訳

_html で終わるキーや html という名前のキーは、ビューで自動的に HTML セーフとして扱われます。

en:
  welcome_html: "<strong>Welcome</strong> to %{app_name}"
  help:
    html: "Need <em>help</em>? <a href='%{url}'>Contact us</a>"
<%= t("welcome_html", app_name: "MyApp") %>  <%# HTML not escaped %>

補間値は引き続きエスケープされるため、XSS に対して安全です。翻訳自体にマークアップが含まれる場合に限り、_html キーを使ってください。

ステップ 4:Active Record モデルを翻訳する
モデル名と属性
en:
  activerecord:
    models:
      user:
        one: "User"
        other: "Users"
      admin/post: "Admin Post"    # Namespaced model
    attributes:
      user:
        email: "Email address"
        first_name: "First name"
      user/role:                   # Nested attribute
        admin: "Administrator"
User.model_name.human              # => "User"
User.model_name.human(count: 2)    # => "Users"
User.human_attribute_name(:email)  # => "Email address"
バリデーションエラーメッセージ

エラーメッセージは次の順序で検索されます(最初に一致したものが使われます)。

activerecord.errors.models.MODEL.attributes.ATTRIBUTE.ERROR
activerecord.errors.models.MODEL.ERROR
activerecord.errors.messages.ERROR
errors.attributes.ATTRIBUTE.ERROR
errors.messages.ERROR
en:
  activerecord:
    errors:
      models:
        user:
          attributes:
            email:
              blank: "is required — we need this to contact you"
              taken: "is already registered"
            name:
              too_short: "must be at least %{count} characters"
          # Applies to all attributes on User:
          invalid: "has a problem"
      # Applies to all models:
      messages:
        blank: "can't be empty"

  # Global fallback for all models:
  errors:
    format: "%{attribute}: %{message}"   # Customize full_message format
    messages:
      blank: "is required"

エラーメッセージで利用できる補間変数:model、attribute、value、count。

ステップ 5:日付、時刻、数値をローカライズする
日付/時刻の書式設定
en:
  date:
    formats:
      default: "%Y-%m-%d"
      short: "%b %d"
      long: "%B %d, %Y"
  time:
    formats:
      default: "%a, %d %b %Y %H:%M:%S %z"
      short: "%d %b %H:%M"
      long: "%B %d, %Y %H:%M"
l(Date.today)                        # Default format
l(Date.today, format: :short)        # Short format
l(Time.current, format: :long)       # Long format

日付/時刻には l() を使います — strftime は現在のロケールを無視するため、英語以外のユーザー向けに日付が正しく書式設定されません。

数値の書式設定

数値ヘルパー(number_to_currency、number_with_delimiter など)はロケールファイルを参照します。

en:
  number:
    format:
      separator: "."
      delimiter: ","
      precision: 3
    currency:
      format:
        unit: "$"
        format: "%u%n"           # $1,000.00
        separator: "."
        delimiter: ","
        precision: 2

es:
  number:
    currency:
      format:
        unit: "€"
        format: "%n %u"          # 1.000,00 €
        separator: ","
        delimiter: "."
ステップ 6:リクエストごとにロケールを設定する

I18n.with_locale を使った around_action を使ってください — I18n.locale = を直接設定すると、スレッドサーバー(Puma)ではリクエスト間で値が漏れ、ほかのユーザーのロケールが表示される原因になります。

URL パスからの取得(推奨)
# config/routes.rb
scope "(:locale)", locale: /en|es|fr/ do
  resources :books
  # ... all routes
end

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  around_action :switch_locale

  def switch_locale(&action)
    locale = params[:locale] || I18n.default_locale
    I18n.with_locale(locale, &action)
  end

  def default_url_options
    { locale: I18n.locale }
  end
end
優先順位を組み合わせたチェーン
def resolve_locale
  params[:locale].presence ||
    current_user&.locale.presence ||
    request.env["HTTP_ACCEPT_LANGUAGE"]&.scan(/^[a-z]{2}/)&.first&.then { |l|
      l.to_sym if I18n.available_locales.include?(l.to_sym)
    } ||
    I18n.default_locale
end
ステップ 7:ロケールファイルを整理する

小規模なアプリ: ロケールごとに 1 つのファイル(config/locales/en.yml、es.yml)。

中規模/大規模なアプリ — 用途ごとに分割します:

config/locales/
  defaults/en.yml    # Date, time, number formats
  models/en.yml      # AR model names, attributes, errors
  views/en.yml       # View translations (lazy lookup keys)
  mailers/en.yml     # Mailer subjects and content

ネストされたディレクトリを読み込む: config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")]

YAML のルール: 最上位のキーはロケールです。キーにはスネークケースを使用します。キーとして使う 'true'/'false'/'yes'/'no'/'on'/'off' は引用符で囲みます(そうしないと YAML はブール値として解析します)。ネストは 4 階層以内にします。

ステップ 8:Action Mailer の翻訳

メーラーの件名は mailer_scope.action_name.subject から自動的に解決されます:

en:
  user_mailer:
    welcome:
      subject: "Welcome to %{app_name}!"
class UserMailer < ApplicationMailer
  def welcome(user)
    mail(to: user.email)  # Subject auto-resolved
    # Or with interpolation: mail(to: user.email, subject: default_i18n_subject(app_name: "MyApp"))
  end
end
ステップ 9:フォールバック
# config/application.rb
config.i18n.fallbacks = true                    # Falls back to default_locale
config.i18n.fallbacks = { es: :en, fr: :en }   # Or specific chains

開発/テスト環境では、フォールバックを OFF にし、 raise_on_missing_translations = true にします。

エージェントがよくする間違い

  1. 文字列をハードコーディングする — t(".success") ではなく "Record saved" と記述する
  2. YAML のネストを誤る — 最上位のロケールキーを忘れる、またはインデントを誤る
  3. 遅延ルックアップを使わない — ビュー/コントローラーで t(".title") ではなく t("users.show.title") を使う
  4. available_locales を設定し忘れる — 設定しないと任意のロケール文字列が受け入れられます(セキュリティリスク)
  5. I18n.locale = を使う — スレッドサーバーではリクエストをまたいで設定が漏れるため、I18n.with_locale を使う
  6. count を指定せずに複数形処理をする — 文字列ではなく生のハッシュが返される
  7. _html サフィックスを付け忘れる — 翻訳内の HTML がエスケープされます
  8. YAML のブール値を引用符で囲まない — true、false、yes、no はブール値として解析されるため、キーとして使う場合は引用符で囲む
  9. サーバーを再起動し忘れる — 新しいロケールファイルを読み込むには再起動が必要です

クイックリファレンス

翻訳のルックアップ方法
コンテキスト方法遅延ルックアップ
ビューt(".key") または t("full.key")✅ はい
コントローラーt(".key") または I18n.t("full.key")✅ はい
モデルI18n.t("full.key")❌ いいえ
メーラーI18n.t("full.key")❌ いいえ
サービス/ジョブI18n.t("full.key")❌ いいえ
ロケールファイルの基本構成
en:
  # View translations (lazy lookups)
  controller_name:
    action_name:
      key: "value"
  # Shared/global
  shared:
    save: "Save"
    cancel: "Cancel"
  # Active Record
  activerecord:
    models:
      model_name: { one: "Singular", other: "Plural" }
    attributes:
      model_name:
        attribute: "Label"
    errors:
      models:
        model_name:
          attributes:
            attribute:
              error_type: "message"
  # Formats (or use rails-i18n gem)
  date:
    formats: { default: "%Y-%m-%d", short: "%b %d", long: "%B %d, %Y" }
  time:
    formats: { default: "%Y-%m-%d %H:%M" }
  number:
    currency:
      format: { unit: "$" }
  # Mailers
  mailer_name:
    action_name:
      subject: "Subject line"
新しいロケールのチェックリスト
  • available_locales に追加する
  • 既存の構成に合わせてロケールファイルを作成する
  • 日付/時刻/数値のデフォルト値と複数形ルールのために rails-i18n gem を追加する
  • raise_on_missing_translations = true を設定してテストする
  • ロケール切り替え UI を追加する。URL ベースの場合は default_url_options も更新する

詳しいパターン、例、注意点については、references/ ディレクトリを参照してください:

  • references/lookups.md — 翻訳のルックアップ方法、補間、遅延ルックアップ
  • references/locale-files.md — YAML のパターン、ファイルの整理、日付/時刻/数値のローカライズ、カスタムバックエンド
  • references/pluralization.md — 言語ごとの複数形ルール
  • references/model-translations.md — Active Record のモデル/属性/エラーの翻訳
  • references/locale-switching.md — ロケール切り替えの方法とフォールバック設定
  • references/testing.md — I18n のテスト、よく使われる gem(rails-i18n、i18n-tasks、mobility)

コメント