如何在 Next.js(Pages Router)v16 中从文件加载翻译内容

将可翻译内容与代码分离

问题

将面向用户的字符串直接硬编码到组件中,会导致内容与代码紧密耦合。每当文案发生变更时,开发者都必须定位并修改实现文件。若要支持第二种语言,需要查找所有硬编码字符串并添加条件逻辑。增加第三种语言时,这种逻辑会进一步复杂化。这种做法让翻译流程依赖于代码部署,阻碍了非技术团队成员独立更新内容。

随着应用规模扩大,管理翻译变得愈发困难。分散在数十个组件中的字符串难以统一审核、去重或一致性更新。由于需要访问代码库,译者无法与开发者并行工作。

解决方案

将所有面向用户的文本存储在外部资源文件中,并按语言组织,每个语言一个 JSON 文件。每条消息都用唯一的 key 标识,而不是直接使用文本。组件引用这些 key,而不是硬编码字符串。

根据 Next.js 提供的 locale 加载相应的翻译文件,并将消息传递给 react-intl 的 provider。应用可以通过加载不同的文件来切换语言,无需更改任何组件代码。这样内容与实现解耦,译者可以在标准 JSON 文件中工作,开发者则引用稳定的消息 key。

步骤

1. 按 locale 创建翻译文件

将翻译消息放在按 locale 分隔的 JSON 文件中。每个文件包含 key-value 对,其中 key 是稳定的标识符,value 是该语言的翻译字符串。

{
"welcome": "Welcome back",
"greeting": "Hello, {name}",
"itemCount": "You have {count, plural, one {# item} other {# items}}"
}

为每个支持的语言环境创建一个文件(例如,messages/en.jsonmessages/es.jsonmessages/fr.json),并将其放在项目根目录下的 messages 目录中。所有文件应使用相同的 key,这样 react-intl 才能根据当前语言环境查找正确的翻译。

2. 在 getStaticProps 中加载消息

根据 Next.js 在 getStaticProps 中接收到的 locale 读取对应的翻译文件。这样可以确保消息在服务端可用,并作为 props 传递给页面。

import { GetStaticProps } from "next";

export const getStaticProps: GetStaticProps = async (context) => {
  const locale = context.locale || "en";
  const messages = (await import(`../messages/${locale}.json`)).default;

  return {
    props: {
      messages,
    },
  };
};

动态导入只会加载当前语言环境对应的文件。Next.js 会根据 URL 或用户偏好自动提供 locale 值。

3. 在 _app 中将消息传递给 IntlProvider

用 IntlProvider 包裹根组件,并配置用户当前的 locale 及对应的翻译消息。通过 pageProps 获取消息,这样每个页面都可以提供自己的翻译内容。

import { AppProps } from "next/app";
import { IntlProvider } from "react-intl";
import { useRouter } from "next/router";

export default function App({ Component, pageProps }: AppProps) {
  const { locale, defaultLocale } = useRouter();

  return (
    <IntlProvider
      locale={locale || "en"}
      defaultLocale={defaultLocale || "en"}
      messages={pageProps.messages}
    >
      <Component {...pageProps} />
    </IntlProvider>
  );
}

Provider 会将消息提供给组件树中的所有组件。每个页面通过 getStaticProps 加载自己的消息文件,_app 通过 pageProps 获取这些消息。

4. 在组件中通过 key 引用消息

使用 react-intl 的 FormattedMessage 组件或 useIntl hook 来显示翻译文本。请通过 key 引用消息,而不是硬编码字符串。

import { FormattedMessage, useIntl } from "react-intl";

export default function HomePage() {
  const intl = useIntl();
  const userName = "Alice";

  return (
    <div>
      <h1>
        <FormattedMessage id="welcome" />
      </h1>
      <p>
        <FormattedMessage id="greeting" values={{ name: userName }} />
      </p>
      <input placeholder={intl.formatMessage({ id: "searchPlaceholder" })} />
    </div>
  );
}

react-intl 会根据给定的 id 查找并格式化翻译消息。如果缺少翻译,则会回退到 defaultMessage(如果有提供)。通过 values prop 传递的变量会被插入到消息字符串中。