mcpField
I
i18n

i18n

github.com/thinkoodle开发7190 次安装8 周前更新
代码仓库

关于 i18n

面向 Rails 开发者的 I18n 实践技能,用于为应用添加国际化和本地化。内容涵盖翻译文件组织、按请求切换语言,以及日期、数字、模型和邮件的本地化。

主要功能

  1. 01使用 locale 文件管理翻译
  2. 02配置语言列表与回退规则
  3. 03采用惰性查找和插值
  4. 04本地化日期、数字及复数
  5. 05翻译模型、验证错误和邮件
  6. 06按请求安全切换语言

适用场景

  1. 01为现有 Rails 应用添加多语言支持
  2. 02整理和拆分 locale 文件
  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 应用添加国际化和本地化。所有面向用户的字符串都应放在 locale 文件中——绝不硬编码。

理念

核心原则:

  1. 所有字符串都放在 locale 文件中 — 不要在视图、控制器、邮件程序或模型中硬编码面向用户的文本
  2. 处处使用惰性查找 — 在视图和控制器中使用 .title,而不是 books.index.title
  3. 按功能而非语言组织 — 按领域拆分 locale 文件(模型、视图、默认值),不要使用一个巨大的文件
  4. YAML 为王 — 除非需要用于日期格式的 Ruby lambda,否则使用 .yml 文件
  5. 在开发和测试环境中及时报错 — 设置 raise_on_missing_translations = true,以便尽早发现缺失的键

何时使用此技能

  • 为现有 Rails 应用添加 I18n 支持
  • 创建或编辑 YAML locale 文件
  • 使用 t() / I18n.t() 和 l() / I18n.l() 辅助方法
  • 设置 locale 切换(URL、子域名、请求头、用户偏好)
  • 翻译 Active Record 模型名称、属性和错误消息
  • 本地化日期、时间、数字和货币
  • 为非英语 locale 设置复数规则
  • 在大型应用中组织 locale 文件
  • 配置回退和可用 locale

说明

第 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 — 如果不设置,任何 locale 字符串都会被接受,可能导致文件系统遍历攻击和意外的回退行为。

第 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 获取特定 locale 的复数规则。

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 会忽略当前 locale,因此非英语用户看到的日期格式将不正确。

数字格式

数字辅助方法(number_to_currency、number_with_delimiter 等)会从 locale 文件中读取设置:

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 步:按请求设置 locale

使用带有 I18n.with_locale 的 around_action — 直接设置 I18n.locale = 会在多线程服务器(Puma)中跨请求泄漏,导致用户看到其他用户的 locale。

从 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:组织语言环境文件

小型应用: 每种语言环境使用一个文件(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 规则: 顶层键 = 语言环境。键使用 snake_case。将 '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

在开发/测试环境中,关闭回退功能,并设置 raise_on_missing_translations = true。

Agent 常见错误

  1. 硬编码字符串 — 使用 "Record saved",而不是 t(".success")
  2. YAML 嵌套错误 — 忘记在顶层添加语言环境键,或缩进错误
  3. 未使用惰性查找 — 在视图/控制器中使用 t("users.show.title"),而不是 t(".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)

评论

支持 Markdown