如何在 React Router v7 中从文件加载翻译
将可翻译内容与代码分离
问题
将面向用户的字符串直接硬编码到组件中,会导致内容与代码之间的紧密耦合。每增加一种新语言,开发人员都需要修改实现文件,扩展条件逻辑,从而增加复杂性。当文案发生变化时,即使是微小的措辞调整也需要重新部署代码。这种方法使得翻译工作流依赖于工程周期,并且阻止了非技术团队成员独立管理内容。
随着应用程序的增长,分散的字符串文本变得难以追踪和维护。在代码库中找到某个短语的每个出现位置容易出错,并且在类似消息中保持一致性几乎是不可能的,除非有一个集中的真源。
解决方案
将所有可翻译的字符串提取到按语言组织的外部 JSON 文件中,每个语言环境一个文件。用消息标识符替换组件中的硬编码字符串,这些标识符引用这些文件中的条目。在运行时,应用程序根据用户的语言环境加载相应的翻译文件,并将这些消息提供给国际化库,国际化库将标识符解析为其翻译值。
这种分离允许翻译人员直接处理 JSON 文件而无需接触代码,使内容更新可以通过简单的文件更改完成,并为每种语言的字符串提供单一的真源。
步骤
1. 为每个语言环境创建翻译文件
在专用目录中组织翻译文件,每种语言一个 JSON 文件。将每个文件结构化为一个平面对象,将消息标识符映射到翻译字符串。
{
"welcome.title": "欢迎回来",
"welcome.subtitle": "继续您上次的进度",
"nav.home": "首页",
"nav.about": "关于",
"nav.contact": "联系"
}
将此文件保存为 app/translations/en.json(英文),然后为其他语言创建类似的文件,例如 app/translations/es.json 和 app/translations/fr.json,并添加相应的翻译。在所有文件中使用一致的键,这样相同的标识符可以在每种语言环境中解析为适当的翻译。
2. 在路由加载器中加载翻译
使用路由加载器在渲染之前获取当前语言环境的翻译文件。这确保了组件挂载时消息是可用的。
import type { Route } from "./+types/root";
import enMessages from "./translations/en.json";
import esMessages from "./translations/es.json";
import frMessages from "./translations/fr.json";
const messages: Record<string, Record<string, string>> = {
en: enMessages,
es: esMessages,
fr: frMessages,
};
export async function loader({ request }: Route.LoaderArgs) {
const url = new URL(request.url);
const locale = url.searchParams.get("locale") || "en";
return {
locale,
messages: messages[locale] || messages.en,
};
}
加载器从 URL 查询参数中读取语言环境,并返回语言环境及其对应的消息。组件可以通过 loaderData 访问这些数据,以配置国际化提供程序。
3. 使用加载的消息配置 IntlProvider
使用 react-intl 的 IntlProvider 包裹您的应用程序,并传递来自加载器数据的语言环境和消息。
import { IntlProvider } from "react-intl";
import { Outlet } from "react-router";
import type { Route } from "./+types/root";
export default function Root({ loaderData }: Route.ComponentProps) {
return (
<IntlProvider locale={loaderData.locale} messages={loaderData.messages}>
<html lang={loaderData.locale}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<Outlet />
</body>
</html>
</IntlProvider>
);
}
IntlProvider 通过 React 上下文将语言环境和消息提供给所有后代组件。子路由通过 Outlet 渲染,并继承对翻译数据的访问。
4. 在组件中通过标识符引用消息
用引用翻译文件中消息标识符的 FormattedMessage 组件替换硬编码字符串。
import { FormattedMessage } from "react-intl";
export default function Welcome() {
return (
<div>
<h1>
<FormattedMessage id="welcome.title" />
</h1>
<p>
<FormattedMessage id="welcome.subtitle" />
</p>
<nav>
<a href="/">
<FormattedMessage id="nav.home" />
</a>
<a href="/about">
<FormattedMessage id="nav.about" />
</a>
<a href="/contact">
<FormattedMessage id="nav.contact" />
</a>
</nav>
</div>
);
}
每个 FormattedMessage 组件在 IntlProvider 提供的消息对象中查找其 id,并渲染对应的翻译字符串。当语言环境更改并且加载器使用不同的消息重新运行时,所有组件会自动显示新的翻译,而无需更改代码。