国际营销

Rails 国际化:从 URL 到 JavaScript

Rails 国际化:从 URL 到 JavaScript
更新于
2026年5月18日

Rails 内置了一个国际化框架。入门并不需要太多准备——I18n API是该技术栈的组成部分,它能很好地处理基础功能:翻译静态字符串、格式化日期和数字,以及在不同语言环境中管理复数形式。

但“内置”并不等于“完整”。一旦您的需求涉及本地化 URL、JavaScript 翻译或多语言 SEO,就意味着您已经超出了 Rails 原生功能所涵盖的范围。这些缺口需要您做出周密的架构决策。

让我们来回顾一下这两个层面:Rails I18n API 负责处理的内容,以及您需要自行实现——或委托给其他组件处理的部分。

Rails I18n 的覆盖范围及其局限性

I18n API 提供了两个核心方法:用于翻译字符串的 I18n.t,以及用于本地化日期和数字的 I18n.l。这两个方法均从 config/locales/ 目录下的 YAML 或 Ruby 文件中读取数据,Rails 会在启动时自动加载这些文件。

开箱即用,这涵盖了静态 UI 字符串、日期和数字格式化,以及 ActiveRecord 验证信息。对于大多数单语种项目而言,这已足以投入使用。

一旦深入探究,这些漏洞就会很快显现出来。

Rails I18n 对 URL 结构不作任何规定:既不支持本地化路由,也不支持子域名检测或路径前缀。它没有机制来翻译数据库中存储的内容,例如产品名称或博客文章。JavaScript 文件完全位于处理流程之外。此外,多语言 SEO(如 hreflang 标签、翻译后的 slug 以及特定语言的站点地图)需要单独处理,框架对此不予支持。

在开始建造之前,你需要先弄清楚边界在哪里。

建立国际化(I18n)基础

你可以先从 rails-i18n gem 开始。

Rails 仅提供英语区域设置数据,因此如果没有这些数据,在非英语区域设置下,I18n.l(Date.today) 将会报错,且框架级别的字符串(如 Active Record 验证信息)无论当前区域设置为何,都将保持为英语。

# Gemfile

gem "rails-i18n"

然后配置应用程序的默认设置:

# config/application.rb

config.i18n.default_locale = :en

config.i18n.available_locales = [:en, :fr]

config.i18n.enforce_available_locales = true

如果您的应用尝试设置列表之外的区域设置,enforce_available_locales = true 将会引发错误。如果不设置此选项,生产环境中因拼写错误或 URL 参数格式错误而导致的未受支持的区域设置会被默默地应用。

虽然现代 Rails 仅扫描一层目录,但它经常会遗漏嵌套较深的文件夹(例如config/locales/views/products/)。添加以下代码可确保随着应用程序的扩展,每个子目录都能被包含进来:

config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")]

这允许您按域名拆分翻译文件:

config/locales/  
	en/    
		models.yml    
		views.yml    
		mailers.yml  
	fr/    
		models.yml    
		views.yml    
		mailers.yml

⚠️ YAML 会将 true 和 false 等值解析为布尔值。如果需要将其作为字面文本使用,请明确加引号。现代 Ruby/YAML 版本现在将 yes 和 no 视为字符串,但出于兼容性考虑,加引号仍是一种安全的做法:

en:  
	答案:    
		是: “是”    
		“不”

这可以捕获一类仅在以下情况下才会出现的错误:当翻译结果返回“true”而非“yes”时,视图既未渲染任何内容,甚至更糟的是,直接渲染了字符串“true”。

Views 中的翻译助手和延迟查找

在视图中,t 和 l 是你会经常用到的两个辅助函数。

l 会根据当前区域设置对日期、时间和数字进行本地化处理。安装 rails-i18n 后,系统会自带 100 多种区域设置的格式定义。若未安装该插件,在非英语区域设置下调用 l(Date.today) 通常会返回“翻译缺失”的提示信息或未格式化的日期,因为 Rails 缺少必要的本地化格式模板。

<%= l(Date.today) %>

<%= l(Date.today, format: :long) %>

t 函数通过键来转换字符串。其完整写法是 t("products.index.title"),但在视图中可以使用懒加载的简写形式:

<%= t(".title") %>

前缀的点号会指示 Rails 根据当前视图路径解析键名。在app/views/products/index.html.erb 中,t(".title") 会自动展开为 t("products.index.title")。这种做法既能保持键名简短,又能确保命名规范的一致性,且无需额外操作。

For interpolation, use %{variable} in your YAML and pass the value as a keyword argument:

en:  
	welcome: "Hello, %{name}"
<%= t(".welcome", name: current_user.name) %>

如果翻译中包含您信任的 HTML 内容,请在键名后添加 _html。Rails 会自动将其标记为安全,无需调用 html_safe 方法。

en:  
	notice_html: "Please <strong>confirm</strong> your email."

延迟查找仅在 Rails 能推断出视图路径时才有效,这意味着它在后台任务和 API 控制器中无法使用。在这些情况下,请每次都使用完整的键。

选择区域检测策略

在编写任何语言环境检测代码之前,请先确定一种策略。主要有五种方法,具体选择哪种取决于您的应用架构和SEO要求。

战略 示例 最适合
URL 参数 /products?locale=fr 内部工具,快速原型制作
路径前缀 /fr/products 需要多语言搜索引擎优化的公共网站
子域 fr.example.com 各具特色的地区品牌
顶级域名 example.fr 您已拥有的国家/地区域名
数据库首选项 存储在用户记录中 已验证且包含用户设置的应用程序

路径前缀(如/fr/products)是面向公众的 Rails 应用程序中最常见的选择。它能在每个 URL 中明确标注语言区域,从而使搜索引擎能够按语言分别进行索引。

当您希望强化区域标识时,子域名和顶级域名策略效果显著,但它们会增加 DNS 配置和 SSL 证书的开销。

对于不涉及搜索引擎优化的内部工具而言,URL 参数(例如/products?locale=fr)是完全可以接受的。

存储在数据库中的首选项适用于已通过身份验证的应用程序,在这些应用程序中,区域设置遵循用户设置,而非 URL。

无论您选择哪种策略,都存在一个会导致生产环境中出现隐蔽错误的实现失误:直接使用 I18n.locale =。

# Don't do this

before_action { I18n.locale = params[:locale] }

I18n.locale = 写入 Thread.current。

如果您使用的是PumaWeb 服务器,它会在不同请求之间复用线程。如果某个请求未显式设置区域设置,它将继承上一个请求留下的设置。

在单线程的本地开发环境中,这个问题从未出现过。但在生产环境中,它会导致间歇性的语言错误响应,这些错误难以复现,更难追踪。

请改在 around_action 中使用 I18n.with_locale:

around_action :switch_locale‍d


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

with_locale 会在代码块退出后恢复之前的区域设置,无论请求如何结束。

实现本地化路由和子域检测

在 /:locale 下定义路由,并覆盖 default_url_options,以便 Rails 自动在每个 URL 助手的前面添加当前语言环境:

# config/routes.rb
scope "/:locale" do
  resources :products
  root "home#index"
end

# app/controllers/application_controller.rb
around_action :switch_locale

def switch_locale(&action)
  locale = params[:locale]
  # Fallback to default if the param is missing or unsupported
  valid_locale = I18n.available_locales.map(&:to_s).include?(locale) ? locale : I18n.default_locale
  I18n.with_locale(valid_locale, &action)
end

def default_url_options
  { locale: I18n.locale }
end

设置 default_url_options 后,当当前语言环境为 :fr 时,products_path 会自动渲染为/fr/products

如果你希望默认语言环境省略前缀,请将该分段设为可选,并指定作用域为“(:locale)”。这样可以为英语提供/products,为法语提供/fr/products,但会增加歧义。例如,路径/about可能匹配缺失的语言环境,也可能匹配名为 about 的控制器,具体取决于路由的排序。

在检测子域名时,请先从 request.subdomains.first 中读取数据,并根据 available_locales 进行验证,然后再进行任何设置:

def 从子域名中提取语言环境
  subdomain = request.subdomains.first
  return nil if subdomain.blank? || subdomain == "www"
  子域名 如果 I18n.available_locales.map(&:to_s).include?(subdomain)
end

www 检查机制会阻止其被视为一个区域设置。在 localhost 上,request.subdomains 会返回一个空数组,因此除非使用 Pow 服务器或在/etc/hosts 中添加条目,否则在开发环境中子域检测将失败。

有一项需要单独配置:在 ApplicationController 中定义的 default_url_options 不会传播到邮件发送器或后台任务中。请在这些上下文中显式设置该选项:

Rails.application.routes.default_url_options = { host: "example.com", locale: :en }

本地化路径会为您提供 URL 结构,但在完成此设置后,hreflang 标签、翻译后的路径和多语言网站地图仍需完全手动处理。

Weglot反向代理集成会自动处理这一层,无需额外配置即可生成 hreflang 标签和特定语言的 URL。

复数形式、备用方案与 rails-i18n Gem

英语的复数形式很简单。单数形式一种,其余情况都用同一种形式。

en:
  messages:
    one: "%{count} message"
    other: "%{count} messages"

大多数语言并没有那么简单。

例如,保加利亚语中,某些阳性词汇既有常规复数形式,也有特殊的“计数复数”形式。因此,“ден”(天)通常变为“дни”(多天),但在计数时则变为“два дена”(两天)。

bg:
  cities:
    one: "%{count} ден"
    few: "%{count} дена"
    many: "%{count} дни"

如果没有 rails-i18n,Rails 无法识别这些规则。因此,您的保加利亚语翻译要么会报错,要么在每次计数时都会退回到另一种形式。

Gem 支持 100 多种语言环境的复数逻辑,因此保加利亚语、俄语、阿拉伯语、波兰语以及任何其他具有非英语复数规则的语言都能正确处理。

备用方案是另一个需要考虑的问题,默认情况下处于关闭状态。如果没有备用方案,生产环境中任何缺失的翻译键都会导致出现“翻译缺失”的提示。这种情况一旦发布,就会出现在截图中。

config/application.rb 中启用备用方案并定义默认目标:

config.i18n.fallbacks = [I18n.default_locale]

当 fallbacks = true 时,当前区域设置中缺失的键将回退到 default_locale。对于区域变体,您可以显式定义链:

config.i18n.fallbacks = { "fr-CA": :fr, "en-GB": :en }

请尽早进行配置。对于已经在生产环境中部署了部分翻译的应用程序,事后添加备用处理机制要比在项目初期就进行配置困难得多。

将翻译结果传递给刺激控制器和 JavaScript

JavaScript 无法访问 Rails 的 I18n 处理流程。

在您的翻译文件与 Stimulus 控制器之间,既没有 t 助手,也没有 YAML 加载器,更没有内置的桥梁。您必须在服务器端显式地传递翻译内容。

对于 Stimulus 而言,最简单的方法是使用值 API。在控制器中将翻译定义为一个值,在 HTML 中设置该值,然后在 JavaScript 中读取它:

# app/views/products/index.html.erb
<div data-controller="notification"
     data-notification-message-value="<%= t('.success_message') %>">

该值使用标准的 t 辅助函数在服务器端进行渲染,因此会自动遵循当前的区域设置。在 JavaScript 端,请将其声明为静态值,并通过 values API 读取:

// app/javascript/controllers/notification_controller.js
export default class extends Controller {
  static values = { message: String }

  show() {
    alert(this.messageValue)
  }
}

每项翻译都明确且仅作用于需要它的控制器。没有任何内容泄漏到全局作用域中。

如果一个控制器需要同时使用多个翻译,将它们打包为 JSON 数据属性比为每个字符串单独添加值定义更为简洁:

<div data-controller="cart"
     data-cart-i18n-value="<%= { add: t('.add'), remove: t('.remove') }.to_json %>">

在 JavaScript 端,将 i18n 声明为 Object 类型,并直接访问各个键:

static values = { i18n: Object }

add() {
  console.log(this.i18nValue.add)
}

对于那些需要 JavaScript 在多个控制器之间广泛访问翻译内容的应用程序,i18n-js gem 会将您的 YAML 文件导出为一个 JavaScript 对象,您可以通过 I18n.t("key") 进行查询。

虽然可行,但这会增加一个构建步骤,并且会将完整的翻译集发送给客户端。当前两种模式变得重复时可以使用它,但不要将其作为默认方案。

在使用 Turbo Frames 时,如果某个 Turbo Frame 的 src URL 省略了语言环境前缀,请求通常会因路由错误 (404) 而失败,因为它无法匹配您的“/:locale”作用域模式。请务必为框架源使用支持语言环境的路径助手。

这并不是 Turbo 的一个错误。实际上,这是路由设置上的疏漏,只要框架中的每个 URL 都使用了支持语言环境的路径助手,来自语言环境检测部分的 around_action 就会阻止这种情况发生。

Weglot反向代理采用了完全不同的方法。它会在页面加载完成后对渲染后的 DOM 进行转换,因此无需任何额外配置,即可覆盖 Stimulus 渲染的内容和动态插入的字符串。

Rails I18n 遗漏了什么

有三类情况完全超出了 Rails I18n 的处理范围:

  • Rails 中的多语言 SEO:i18n 虽然能翻译字符串,但不会自动生成 hreflang 标签、特定语言的站点地图或翻译后的元数据。这些任务需要在完成 i18n 配置后进行手动操作。
  • 存储在数据库中的内容(如产品名称和博客文章)无法保存在 YAML 文件中。建议使用Mobility gem来处理此类情况,因为它支持多种存储后端。对于具有按列翻译功能的小型模型,Traco是一个更轻量级的替代方案。
  • Rails 中的翻译工作流依赖于 YAML 文件。管理翻译流程、审阅周期、更新,以及确保翻译与应用程序保持同步,这些工作必须在框架外部进行。

Weglot 在输出层而非代码层解决了所有这些问题:

  • 它并非与 Rails 的 I18n 管道集成,而是对应用程序生成的已渲染 HTML 进行翻译。这意味着数据库内容、JavaScript 渲染的字符串以及静态翻译都会以相同的方式进行处理。
  • 包含多语言SEO/GEO功能:hreflang标签、翻译后的URL以及网站地图均会自动生成。
  • 翻译工作流通过Weglot仪表盘进行,而非通过YAML导出流程。

话虽如此,在您对其进行评估之前,有几点限制值得注意:

  • 该 JavaScriptsnippet 不会被搜索引擎收录;若要让翻译后的页面出现在搜索结果中,您需要配置反向代理。
  • 自定义子目录路由功能仅适用于企业版,若您已有现成的 CDN 或 Nginx 配置,此功能将非常实用。
  • 与直接嵌入代码库的翻译不同,Weglot 保持订阅状态,才能让翻译后的内容保持可见。

选择您的 Rails i18n 架构

每一种 Rails 的 i18n 实现,归根结底都涉及相同的决策,且决策顺序大致相同。

首先,在编写任何区域检测代码之前,请先确定您的 URL 策略。对于大多数面向公众的应用程序,路径分段是一种有效的方案。当区域标识至关重要时,使用子域名更为合理。对于需要用户认证的应用程序,将偏好设置存储在数据库中更为合适,因为此时区域设置应随用户而定,而非由 URL 决定。

其次,决定 JavaScript 如何获取翻译内容。Stimulus 值 API 涵盖了大多数情况。当控制器需要同时获取多个字符串时,可以使用 JSON 属性。

第三,决定哪些部分需要手动构建。Rails 在处理静态字符串方面表现出色。SEO 层、翻译工作流以及数据库内容都需要分别进行决策。您可以自行构建这些部分,也可以将输出层交由Weglot 等工具Weglot 开发时间集中投入到应用程序本身。

完全跳过输出层。 Weglot 试用Weglot 14 天,无需修改您的 i18n 代码,即可享受多语言 SEO/GEO 优化、翻译后的 URL 以及自动内容检测功能。

方向图标
探索 Weglot

 110,000 多个品牌都在用 Weglot 翻译自己的网站,赶紧加入吧!

用AI即时翻译你的网站,再通过人工编辑进行优化,几分钟内就能上线。

这篇文章里,我们会聊聊:
火箭图标

准备好开始了吗?

要Weglot 强大功能Weglot 最好的方式Weglot 亲自体验。立即免费试用,无需任何承诺。

若您尚未准备好连接自己的网站,控制面板中已提供演示网站。

你可能也会喜欢这些文章

常见问题图标

常见问题

没有找到任何内容。

蓝色箭头

蓝色箭头

蓝色箭头