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

将可翻译内容与代码分离

问题

将面向用户的字符串直接硬编码到组件中,会导致内容与代码之间的紧密耦合。每次文案发生变化时,开发人员必须找到并修改实现文件。添加对第二种语言的支持需要找到每个硬编码的字符串并将其包装在条件逻辑中。添加第三种语言会进一步扩展这种逻辑。这种方法使得翻译工作流依赖于代码部署,并且阻止非技术团队成员独立更新内容。

随着应用程序的增长,管理翻译变得越来越困难。分散在数十个组件中的字符串很难进行审计、重复或一致地更新。翻译人员无法与开发人员并行工作,因为他们需要访问代码库本身。

解决方案

将所有面向用户的文本存储在外部资源文件中,按语言组织,每个语言环境一个 JSON 文件。每条消息通过唯一的键标识,而不是其文字内容。组件引用这些键,而不是硬编码的字符串。

根据从 Next.js 接收到的语言环境加载相应的翻译文件,并将消息传递给 react-intl 的提供程序。应用程序可以通过加载不同的文件来切换语言,而无需更改任何组件代码。这将内容与实现解耦,使翻译人员可以在标准 JSON 文件中工作,而开发人员则引用稳定的消息键。

步骤

1. 创建按语言环境组织的翻译文件

将翻译消息放入按语言环境分隔的 JSON 文件中。每个文件包含键值对,其中键是稳定的标识符,值是该语言的翻译字符串。

{
"welcome": "欢迎回来",
"greeting": "你好,{name}",
"itemCount": "你有 {count, plural, one {# 件物品} other {# 件物品}}"
}

为每个支持的语言环境创建一个文件(例如,messages/en.jsonmessages/es.jsonmessages/fr.json),并将其放在项目根目录下的 messages 目录中。在所有文件中使用相同的键,以便 react-intl 可以查找活动语言环境的正确翻译。

2. 在 getStaticProps 中加载消息

根据从 Next.js 接收到的 locale,在 getStaticProps 中读取翻译文件。这确保了消息在服务器端可用,并作为 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,
    },
  };
};

动态导入仅加载当前 locale 的文件。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>
  );
}

该提供程序使树中的所有组件都可以访问消息。每个页面通过 getStaticProps 加载其自己的消息文件,而 _app 通过 pageProps 接收这些消息。

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

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

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 属性传递的变量会被插入到消息字符串中。