# General Translation Knowledge Base: 如何为 Next.js Pages Router 应用添加 i18n？
URL: https://generaltranslation.com/zh/kb/add-i18n-to-nextjs-pages-router.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: 在 next.config.js 中添加包含 locales 和 defaultLocale 的 i18n 配置块后，Next.js 便会提供 /es 和 /fr 等路径，根据浏览器语言重定向访问者，并将区域设置传递给 getStaticProps 和 getServerSideProps。消息由库负责渲染；General Translation 的 gt-next 还能根据直接以内联方式写在 JSX 中的文案生成译文。
Category: 国际化
Last updated: 2026-10-28
Authors: General Translation Team
Published: 2026-10-28

自 Next.js 10 起，Pages Router 就内置了国际化路由：只需在 `next.config.js` 的 `i18n` 配置块中列出支持的区域设置和默认区域设置，Next.js 便会为每种语言提供独立的 URL，根据浏览器语言重定向首次访问的用户，并将当前区域设置传递给你的数据获取函数。不过，它不会加载、渲染或翻译任何文本：Next.js 的路由本就是为配合国际化库使用而设计的，因此你仍需选择一个库来处理消息，并确定生成译文的方式 ([Next.js Pages Router 国际化](https://nextjs.org/docs/pages/guides/internationalization)) 。

## 内置 i18n 路由的作用

整个功能就是一个配置块：

```js title="next.config.js"
/** @type {import('next').NextConfig} */
module.exports = {
  i18n: {
    // 站点支持的所有区域设置（含默认区域设置）
    locales: ['en', 'es', 'fr'],
    // 该区域设置的路径不带前缀：/about、/es/about、/fr/about
    defaultLocale: 'en',
  },
};
```

配置完成后，Next.js 会负责以下四项工作：

* **URL。** 子路径路由会让默认区域设置保持在 `/about`，其余区域设置则加上前缀 (`/fr/about`) 。如果想改为通过独立域名 (如 `example.fr`) 提供各区域设置，可以添加 `domains` 列表，每个条目包含一个 `domain` 和一个 `defaultLocale`。
* **检测。** 访问 `/` 时，Next.js 会读取 `Accept-Language` 请求头，并重定向到匹配的前缀或域名。由语言切换器设置的 `NEXT_LOCALE` cookie 优先于该请求头。设置 `localeDetection: false` 即可关闭重定向。
* **代码中的区域设置。** `useRouter()` 会返回 `locale`、`locales` 和 `defaultLocale`。`getStaticProps` 和 `getServerSideProps` 会在其 context 中接收 `locale`，`getStaticPaths` 则接收 `locales`。
* **`<html lang>`。** Next.js 会将该属性设为当前区域设置，除非你的 `_document` 自行设置了该属性。

有了这些，就足以手动实现 i18n。本页会在构建时为每个区域设置加载一个 JSON 文件：

```tsx title="pages/index.tsx"
import type { GetStaticProps, InferGetStaticPropsType } from 'next';
import Link from 'next/link';
import { useRouter } from 'next/router';

export const getStaticProps = (async ({ locale }) => {
  // 在构建时，Next.js 会为每个已配置的区域设置调用一次此函数。
  const messages = (await import(`../messages/${locale}.json`)).default;
  return { props: { messages } };
}) satisfies GetStaticProps;

export default function Home({ messages }: InferGetStaticPropsType<typeof getStaticProps>) {
  const { locales } = useRouter(); // ['en', 'es', 'fr']

  return (
    <main>
      <h1>{messages.title}</h1>
      <nav>
        {locales?.map((code) => (
          // 同一页面的各语言版本：/、/es、/fr
          <Link key={code} href="/" locale={code} lang={code}>
            {new Intl.DisplayNames([code], { type: 'language' }).of(code)}
          </Link>
        ))}
      </nav>
    </main>
  );
}
```

在生产环境构建中，访问 `/es` 会返回带有西班牙语标题的 `<html lang="es">` 页面；而携带 `Accept-Language: fr-CA` 请求 `/` 时，会收到指向 `/fr` 的 307 重定向。各链接均使用该语言自身的名称 (English、español、français) ，这正是[语言切换器](/kb/language-switcher)应有的做法。

### 它留给你处理的部分

路由的职责止于 URL。复数形式、数字和日期格式化以及字符串插值都需要交给库来处理，组件变化时保持消息文件同步也是如此。Next.js 也不会在各语言版本之间添加 `hreflang` 链接，你需要通过 `next/head` 自行添加。 (参见[什么是 hreflang？](/kb/what-is-hreflang)。) 此外，以上这些都不会帮你生成任何翻译。

做规划时，有三个限制需要注意：

* **不支持静态导出。** 国际化路由依赖 Next.js 的路由层，因此无法与 `output: 'export'` 搭配使用。
* **构建时间随区域设置数量增长。** 每个非动态的 `getStaticProps` 页面都会针对每个区域设置各构建一次，因此 50 个区域设置乘以 10 个页面就是 500 次调用。动态路由需要在 `getStaticPaths` 中返回每个区域设置，否则 Next.js 只会构建默认区域设置。
* **数量上限。** `locales` 和 `domains` 最多各包含 100 个条目。

### 它无法沿用到 App Router

`i18n` 配置是 Pages Router 的功能。在 App Router 中，`next/navigation` 提供的 `useRouter` 不再包含 `locale`、`locales` 和 `defaultLocale`，Next.js 也明确表示，`app` 目录中无需使用其内置的 i18n 功能 ([迁移指南](https://nextjs.org/docs/app/guides/migrating/app-router-migration#step-5-migrating-routing-hooks)) 。App Router 应用的做法是将区域设置放在 `[locale]` 路由段中，或在中间件中解析区域设置。 (参见[如何为 Next.js App Router 应用添加 i18n？](/kb/add-i18n-to-nextjs)。)

## 负责渲染消息的库

| 库                                                                          | Pages Router 初始化设置                                                                                                                                   | 如何编写文案                                                                                                   | 译文来源                                                                       |
| -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| [next-i18next](https://github.com/i18next/next-i18next#pages-router-setup) | 用 `appWithTranslation` 包裹 `_app`；在 `getStaticProps` 或 `getServerSideProps` 中调用来自 `next-i18next/pages` 的 `serverSideTranslations(locale, ['common'])` | 在 JSON 命名空间中定义键，通过 `t('title')` 渲染                                                                       | 你自己、译者，或 Locize 等翻译管理系统 (TMS)                                              |
| [next-intl](https://next-intl.dev/docs/getting-started/pages-router)       | 在 `_app` 中使用 `NextIntlClientProvider`，并传入 `getStaticProps` 返回的 `messages`                                                                            | 每个区域设置对应一个 JSON 文件，在其中定义键，通过 [`useTranslations`](/docs/react/reference/hooks/use-translations) 渲染        | 你自己或你的译者                                                                   |
| [react-intl](https://formatjs.github.io/docs/react-intl)                   | 在 `_app` 中使用 `IntlProvider`，并传入当前区域设置及其消息                                                                                                            | 以 `defaultMessage` 形式内联编写 ICU 消息，通过 `formatjs extract` 提取                                                | 你自己、译者或 TMS                                                                |
| General Translation (`gt-next`)                                            | 在每个页面上使用 `withGTServerSideProps` 或 `withGTStaticProps`；在 `_app` 中使用 [`GTProvider`](/docs/react/reference/components/gt-provider)                     | 用 [`<T>`](/docs/react/reference/components/t) 包裹 JSX，字符串使用 [`useGT`](/docs/react/reference/hooks/use-gt) | 由 [`npx gt translate`](/docs/cli/reference/commands/translate) 生成，并在仪表板中审校 |

next-i18next 16 同时支持两种路由器，并在 `next-i18next/pages` 下保留了大家熟悉的 Pages Router API。next-intl 的文档推荐使用 App Router，但仍完整支持 Pages Router。在 Pages Router 中，它们都需要配合上文的 `i18n` 配置块来处理 URL 和语言检测。

如果你已有 i18next 命名空间，或想利用其插件生态，请选择 next-i18next。如果你计划迁移到 App Router，并希望迁移前后沿用同一套 API，请选择 next-intl。如果你的消息是译者已经熟悉的 ICU 字符串，请选择 react-intl。如果你更希望将文案直接内联在组件中，并让译文随代码变更自动生成、审校和更新，请选择 General Translation。 (两者的详细对比请参阅 [next-intl vs. gt-next](/kb/next-intl-vs-gt-next)。) 

如果你继续使用 next-i18next 或 next-intl，也无需迁移，General Translation 的 CLI 同样可以翻译它们的 JSON 文件 (包括 ICU 字符串)  (参见 [JSON 文件翻译](/docs/cli/reference/formats/json-files)) 。

## 为 Pages Router 应用添加 General Translation

本初始化设置会为 Pages Router 应用添加西班牙语和法语支持，每种语言都使用独立的 URL。测试环境为：使用 `create-next-app` 16.4.3 (`--no-app`) 创建的应用、Next.js 16.4.3、`gt-next@11.4.16` 和 `gt@2.26.11`。`gt-next` 支持 Next.js 13.0.0 及更高版本 (15.2.1 和 15.2.2 除外) 。其 `gt-next/server` 入口仅适用于 App Router。

### 1. 安装库和 CLI

```bash
npm install gt-next@11.4.16
npm install --save-dev gt@2.26.11
```

### 2. 设置语言

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["en", "es", "fr"],
  "files": {
    "gt": {
      "output": "public/_gt/[locale].json"
    }
  }
}
```

请在 `locales` 中保留 `en`：Next.js 的 `i18n` 配置块也使用这份列表，而它要求必须包含默认区域设置。

### 3. 启用 Next.js i18n 路由

```ts title="next.config.ts"
import type { NextConfig } from 'next';
import { withGTConfig } from 'gt-next/config';
import gtConfig from './gt.config.json';

const nextConfig: NextConfig = {
  i18n: {
    locales: gtConfig.locales,
    defaultLocale: gtConfig.defaultLocale,
  },
};

export default withGTConfig(nextConfig);
```

Next.js 会负责上述路由和检测，因此无需 `gt-next` 中间件，也无需 `pages/[locale]` 文件夹。

`create-next-app` 会在 `pages/_document.tsx` 中写入 `<Html lang="en">`，这会覆盖区域设置：在测试应用中，访问 `/es` 返回的是 `lang="en"`，内容却是西班牙语。删除该属性，交由 Next.js 自动设置：

```tsx title="pages/_document.tsx"
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}
```

### 4. 从本地文件加载翻译

```ts title="loadTranslations.ts"
export default async function loadTranslations(locale: string) {
  try {
    const translations = await import(`./public/_gt/${locale}.json`);
    return translations.default;
  } catch {
    // 在文件生成之前，渲染源文本
    return {};
  }
}
```

`withGTConfig` 会在项目根目录或 `src/` 目录中查找 `loadTranslations.ts`。译文会打包进构建产物，因此应用在运行时不会调用 General Translation。 (参见[存储译文](/docs/react/guides/storing-translations)。)

### 5. 将区域设置和译文传递给每个页面

用包装器包裹每个页面的数据函数。该包装器会从 Next.js 读取 `context.locale`，并将 `locale` 和 `translations` 添加到页面属性中：

```tsx title="pages/about.tsx"
import { T, withGTStaticProps } from 'gt-next';

// Next.js 会为每个区域设置各构建一次此页面。
export const getStaticProps = withGTStaticProps();

export default function About() {
  return (
    <T>
      <h1>About us</h1>
    </T>
  );
}
```

在服务器端渲染的页面上，以同样的方式使用 `withGTServerSideProps()`。两者都接受你自己的数据函数作为参数，例如 `withGTStaticProps(async (context) => ({ props: { posts } }))`，并会将 `redirect` 或 `notFound` 结果原样透传。所有渲染已翻译内容的页面都需要用它包裹。

然后在 `_app` 中将这些属性传给 [`GTProvider`](/docs/react/reference/components/gt-provider)：

```tsx title="pages/_app.tsx"
import type { AppProps } from 'next/app';
import Router from 'next/router';
import { GTProvider, type WithGTServerSideProps } from 'gt-next';

export default function App({ Component, pageProps }: AppProps<WithGTServerSideProps>) {
  const { locale, translations } = pageProps;

  return (
    <GTProvider
      locale={locale}
      translations={translations}
      // 通过 Next.js 导航切换语言：/es、/fr，或切回 /
      _reload={({ locale: nextLocale }) => {
        void Router.push(Router.pathname, Router.asPath, { locale: nextLocale });
      }}
    >
      <Component {...pageProps} />
    </GTProvider>
  );
}
```

### 6. 标记需要翻译的内容

```tsx title="pages/index.tsx"
import { LocaleSelector, T, useGT, withGTServerSideProps } from 'gt-next';

export const getServerSideProps = withGTServerSideProps();

export default function Home() {
  const gt = useGT();

  return (
    <main>
      <LocaleSelector />
      <T>
        <h1>Find your next project</h1>
        <p>Explore projects from teams around the world.</p>
      </T>
      <input type="search" placeholder={gt('Search projects')} aria-label={gt('Search projects')} />
    </main>
  );
}
```

[`<T>`](/docs/react/reference/components/t) 会将标题和段落一并翻译，并以其中的标记作为上下文。[`useGT`](/docs/react/reference/hooks/use-gt) 负责处理属性中的字符串。[`<LocaleSelector>`](/docs/react/reference/components/locale-selector) 会用各语言自身的名称列出你的区域设置。选择 Español 后会保存 `NEXT_LOCALE=es`，Next.js 会在下次访问 `/` 时读取该值，同时 `_reload` 回调会将页面跳转到 `/es`。

### 7. 生成译文并检查构建

使用 [`npx gt login`](/docs/cli/reference/commands/login) 登录，设置 `GT_PROJECT_ID`，然后运行 [`npx gt translate`](/docs/cli/reference/commands/translate)，生成 `public/_gt/es.json` 和 `fr.json`。如果想在不注册账户的情况下试用这套初始化设置，可以运行 [`npx gt generate`](/docs/cli/reference/commands/generate)，它会生成相同的文件，但内容为英文源文本，需要你手动翻译。

在测试应用中，填好西班牙语文件后，`next build` 构建成功，`next start` 的结果如下：

* `/` 返回 `<html lang="en">`，显示英文标题
* `/es` 返回 `<html lang="es">`，显示西班牙语标题和占位符
* `/es/about` (使用 `withGTStaticProps` 构建) 以西班牙语显示
* 带 `Accept-Language: es` 请求头访问 `/` 时，返回 307 重定向到 `/es`

## 发布并保持译文同步更新

每次构建前先执行翻译，确保新增文案能以所有语言同步发布：

```json title="package.json"
{
  "scripts": {
    "build": "gt validate && gt translate && next build"
  }
}
```

在你的托管服务商中设置 `GT_PROJECT_ID`，并将一个具有 **Files &gt; Write** 和 **Translation queue &gt; Enabled** 权限的项目 API 密钥设置为 `GT_API_KEY`。该密钥只能保留在服务器端，切勿为其添加 `NEXT_PUBLIC_` 前缀。如果每次构建都会重新生成 `public/_gt/`，请将其添加到 `.gitignore`。[`gt validate`](/docs/cli/reference/commands/validate) 会在发起任何 API 调用之前检查内容，一旦发现会导致翻译出错的内容，就让构建失败；后续每次运行只会翻译新增或变更的源内容。

审校人员可以在 General Translation 的[翻译编辑器](/docs/platform/dashboard/guides/reviewing-translations)中修改任意译文，[上下文分组](/docs/platform/dashboard/guides/defining-context-for-translations)则可确保词汇表术语和语气与你的其他项目保持一致。若希望各语言版本能被搜索引擎索引，请添加 `hreflang`、规范网址和翻译后的标题；[Next.js 多语言 SEO](/kb/multilingual-nextjs-seo) 对此逐一做了介绍。

## 常见问题

### next.config.js 中的 i18n 选项在 App Router 中是否有效？

不行。它是 Pages Router 专属的功能；App Router 应用需使用 `[locale]` 路由段或中间件，`gt-next` 也为 App Router 单独提供了初始化设置文档。

### 可以在 gt-next 中使用 getStaticProps 吗？

可以。用 `withGTStaticProps` 包裹后，Next.js 会为每个区域设置分别生成一个页面版本。对于动态路由，`getStaticPaths` 仍需为每个区域设置各返回一个路径。

### Pages Router 的国际化需要中间件吗？

不需要。Next.js 会在数据函数运行之前，根据 URL、`NEXT_LOCALE` cookie 和 `Accept-Language` 请求头解析区域设置。

## 要点总结

* `next.config.js` 中的 `i18n` 配置块可为 Pages Router 提供：带区域设置的 URL、访问 `/` 时基于 `Accept-Language` 的重定向、`NEXT_LOCALE` cookie，以及 `useRouter()` 和数据获取函数中的 `locale`。
* 它不会渲染或翻译消息，不会添加 `hreflang`，不支持 `output: 'export'`，也不适用于 App Router。
* 请移除 `pages/_document.tsx` 中硬编码的 `lang="en"`，否则所有区域设置的 HTML 都会被声明为英语。
* next-i18next、next-intl 和 react-intl 只负责渲染你提供的译文；General Translation 的 `gt-next` 还能自动生成译文，只需在每个页面上使用 `withGTStaticProps` 或 `withGTServerSideProps`，并在 `_app` 中添加 [`GTProvider`](/docs/react/reference/components/gt-provider)。
* 在 `next build` 之前加上 [`gt validate && gt translate`](/docs/cli/reference/commands/validate)，确保每次发布都涵盖所有语言。

## Related questions

- [如何为 Next.js App Router 应用添加 i18n？](https://generaltranslation.com/zh/kb/add-i18n-to-nextjs.mdx): 首先在受支持的 App Router 项目中运行 gt init 来配置 General Translation，然后标记源内容、生成译文并验证语言切换。gt-next 将文案与翻译上下文一同保留在 JSX 中；CLI、仪表板编辑器和 Locadex 负责生成、审校和持续更新。本指南还演示了如何显式配置本地文件。
- [next-intl 替代方案：gt-next 与 next-intl 对比](https://generaltranslation.com/zh/kb/next-intl-vs-gt-next.mdx): next-intl 负责渲染你在各语言消息目录中提供的译文。gt-next 同样会渲染译文，此外，General Translation 还能根据你在 JSX 中内联编写的文案生成译文。两者均采用 MIT 许可证，并支持 App Router 和服务端组件。如果你现有的 next-intl 目录运行良好，可以继续保留，并直接用 General Translation 翻译，无需迁移。
- [如何为多语言 Next.js 应用设置 SEO？](https://generaltranslation.com/zh/kb/multilingual-nextjs-seo.mdx): 为每种语言提供带前缀的独立 URL，例如 /en/about 和 /es/about，然后为每种已发布的语言提供 html lang、自引用的规范网址、包含 x-default 的 hreflang 替代项、翻译后的 title 和 description，以及站点地图条目。将未发布的版本标记为 noindex。General Translation 的 gt-next 会将每种语言路由到各自的 URL，并通过 getGT 翻译标题。
- [语言切换器最佳实践：用户体验、SEO 与无障碍访问](https://generaltranslation.com/zh/kb/language-switcher.mdx): 用每种语言的本语名称来标注它，例如 Deutsch 或 日本語，不要使用国旗，因为国旗代表的是国家。每个选项都应通过可抓取的链接指向该语言版本的同一页面，用 cookie 记住用户的选择，并且切勿对已本地化的 URL 自动重定向。
- [i18next 替代方案：General Translation 与 i18next 对比](https://generaltranslation.com/zh/kb/general-translation-vs-i18next.mdx): 如果你希望将文案内联在 React 组件中，并借助其 JSX 结构和产品上下文生成译文，请选择 General Translation。你也可以继续使用 i18next，并用 General Translation 翻译现有的目录。i18next 负责消息渲染和复数形式选择，译文则需要由单独的服务生成。

## Previous and next questions

- Previous question: [什么是翻译记忆库？有了 AI 还需要它吗？](https://generaltranslation.com/zh/kb/what-is-translation-memory.mdx)
- Next question: [如何为 TanStack Start 添加 i18n？](https://generaltranslation.com/zh/kb/add-i18n-to-tanstack-start.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/zh/kb/sitemap.md) for every article.
