Skip to main content

Next.js 国际化完整指南

为 App Router 设置 next-intl、配置语言路由,并利用 AI 自动翻译。

1

安装 next-intl

next-intl 是一个用于 Next.js App Router 的软件包,负责语言路由、消息加载和翻译 hook。

Terminal
npm install next-intl
为什么选择 next-intl 而不是 next-i18next?next-intl 专为 App Router 和服务器组件打造。next-i18next 原本为 Pages Router 设计,对 App Router 的支持有限。
2

创建 i18n 请求配置

创建两个文件:src/i18n/request.ts 用于加载消息,src/i18n/routing.ts 用于定义语言。它们用于配置 next-intl 解析消息和路由的方式。

src/i18n/routing.ts
// src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
import { createNavigation } from 'next-intl/navigation';

export const routing = defineRouting({
  locales: ['en', 'de', 'ja', 'es'],
  defaultLocale: 'en',
  localePrefix: 'as-needed',  // /about for en, /de/about for de
});

export const { Link, redirect, usePathname, useRouter } =
  createNavigation(routing);
Turbopack(Next.js 15 的默认打包器)要求在 next.config.js 中配置 experimental.turbo.resolveAlias,否则会出现 "Couldn't find next-intl config file" 错误。
3

配置中间件

添加 middleware.ts 以处理语言检测、URL 重写和重定向。中间件会拦截每个请求,并确保使用正确语言。

middleware.ts
// middleware.ts  <- Must be in project ROOT, not src/
import createMiddleware from 'next-intl/middleware';
import { routing } from './src/i18n/routing';

export default createMiddleware(routing);

export const config = {
  matcher: ['/((?!api|_next|.*\\..*).*)'],
};
middleware.ts 必须位于项目根目录,而不能放在 src/ 中。这是 next-intl 最常见的配置错误。
4

设置 [locale] 文件夹结构

将应用路由移入 app/[locale]/。添加 generateStaticParams,在构建时为每种语言生成页面。这样会创建 /en/about、/de/about 等 URL 结构。

app/[locale]/layout.tsx
// app/[locale]/layout.tsx
import { routing } from '@/i18n/routing';

export function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}
每个使用译文的 page.tsx 和 layout.tsx 都必须调用 setRequestLocale(locale)。否则 Next.js 会回退到动态渲染,构建性能会显著下降。
5

更新根布局

使用 getMessages() 加载消息,并在根语言布局中传给 NextIntlClientProvider。根据语言参数设置 html 的 lang 属性。

app/[locale]/layout.tsx
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, setRequestLocale } from 'next-intl/server';
import { routing } from '@/i18n/routing';
import { notFound } from 'next/navigation';

export function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  const { locale } = await params;
  if (!routing.locales.includes(locale as any)) notFound();

  setRequestLocale(locale);
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}
NextIntlClientProvider 要求明确提供 locale 属性。省略该属性会在客户端组件中造成难以调试的细微错误。
6

在组件中使用译文

服务器组件使用 getTranslations(异步,需 await),客户端组件使用 useTranslations(hook)。应根据组件渲染位置选择;服务器组件能让译文完全不进入 JavaScript bundle。

app/[locale]/page.tsx
// Server Component (default)
import { getTranslations, setRequestLocale } from 'next-intl/server';

export default async function AboutPage({
  params,
}: { params: { locale: string } }) {
  const { locale } = await params;
  setRequestLocale(locale);
  const t = await getTranslations('AboutPage');

  return <h1>{t('title')}</h1>;
}

// Client Component ('use client')
'use client';
import { useTranslations } from 'next-intl';

export default function SearchBar() {
  const t = useTranslations('SearchBar');
  return <input placeholder={t('placeholder')} />;
}
翻译内容应优先使用服务器组件。它们不会把翻译字符串放入客户端 JavaScript bundle,可减少用户加载时间。
7

添加 SEO:元数据与 Hreflang

使用 generateMetadata 生成特定语言的页面标题和描述。为 hreflang 标签添加 alternates.languages,让搜索引擎发现每个页面的所有语言版本。

app/[locale]/layout.tsx
// app/[locale]/layout.tsx or any page.tsx
import { getTranslations } from 'next-intl/server';
import { routing } from '@/i18n/routing';

export async function generateMetadata({
  params,
}: { params: { locale: string } }) {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: 'Metadata' });

  return {
    title: t('title'),
    description: t('description'),
    alternates: {
      languages: Object.fromEntries(
        routing.locales.map((l) => [l, `/${l}`])
      ),
    },
  };
}
如果未设置 metadataBase,Vercel 构建可能会把 localhost 生成为规范 URL。请务必在根布局中将 metadataBase 设置为生产域名。
8

处理错误页和未找到页面

error.tsx 和 not-found.tsx 可能在正常语言布局之外渲染,因此需要特殊处理。根 not-found.tsx 需要单独设置 i18n provider 才能显示本地化错误消息。

app/[locale]/error.tsx
// app/[locale]/error.tsx
'use client';
import { useTranslations } from 'next-intl';

export default function Error() {
  const t = useTranslations('Error');
  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

// app/not-found.tsx (root level -- needs own provider)
import { routing } from '@/i18n/routing';

export default async function GlobalNotFound() {
  return (
    <html lang={routing.defaultLocale}>
      <body>
        <h1>404 - Page Not Found</h1>
      </body>
    </html>
  );
}
只有代码明确调用 notFound() 时,Next.js 才会渲染本地化 404 页面。没有匹配页面的未知路由会显示默认 Next.js 404,而不是您的本地化版本。
9

自动翻译

完成 i18n 设置后,直接从 IDE 使用 AI 翻译消息文件,或在 CI/CD 管线中使用 i18n Agent CLI,在每次部署时自动翻译。

Terminal
# In your IDE, ask your AI assistant:
> Translate messages/en.json to German, Japanese, and Spanish

✓ messages/de.json created (1.1s)
✓ messages/ja.json created (1.4s)
✓ messages/es.json created (1.0s)
使用 next-intl-localechain 实现智能语言回退:当巴西葡萄牙语不可用时,pt-BR 用户会看到 pt-PT 译文,而不是回退到英语。

自动保证翻译质量

使用 i18n-validate 在发布前发现缺失键和损坏的占位符。真实译文完成前,可使用 i18n-pseudo 生成伪译文来测试 UI。

Next.js i18n 开源工具

这些开源软件包可解决 Next.js 国际化工作流中的常见痛点。

next-intl-localechain

标准 next-intl 在缺少译文时会直接回退到默认语言。巴西葡萄牙语用户会看到英语,而不是完全可用的 pt-PT 译文。next-intl-localechain 会添加智能回退链,深度合并相关语言的译文,让区域用户始终看到最接近的可用译文。

src/i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
import { withLocaleChain } from 'next-intl-localechain';

export default getRequestConfig(withLocaleChain({
  loadMessages: (locale) =>
    import(`../../messages/${locale}.json`).then(m => m.default),
  defaultLocale: 'en'
}));
自动深度合并语言回退链中的译文
内置葡萄牙语、西班牙语、法语、德语等语言的回退链
平稳跳过缺失的消息文件,不会报错
一行设置,用于包装现有 getRequestConfig
在 GitHub 上查看

@i18n-agent/cli

一款命令行工具,让您无需离开终端即可翻译 Next.js 消息文件。可直接翻译文件、检查任务状态并下载结果。通过 API 密钥进行身份验证后,可在 CI/CD 管线中实现全自动本地化工作流。

Terminal
# Install the CLI
npm install -g @i18n-agent/cli

# Authenticate
i18nagent login

# Translate your message files
i18nagent translate ./messages/en.json --lang de,ja,es

# Or use in CI/CD with an API key
export I18N_AGENT_API_KEY=your-key-here
i18nagent translate ./messages/en.json --lang de,ja,es
从终端翻译 JSON、YAML、PO 等 i18n 文件格式
可用于 CI/CD,通过环境变量进行身份验证以实现自动化管线
跟踪任务状态、恢复失败任务并下载结果
提供机器可读的 JSON 输出,用于脚本和自动化
在 GitHub 上查看

常见问题

"Unable to find next-intl locale"

中间件没有匹配请求。请检查:middleware.ts 是否位于项目根目录?matcher 模式是否正确排除静态文件?路由配置中是否包含该语言?

意外的动态渲染

页面或布局缺少 setRequestLocale(locale)。如果未调用,next-intl 会使用请求头或 Cookie 检测语言,从而强制动态渲染并阻止静态生成。

并行路由与 i18n 冲突

并行路由(@modal)和拦截路由((.)photo)与 [locale] 动态区段存在已知兼容问题。这些高级路由模式可改用基于中间件的路由作为变通方案。

切换语言后丢失当前路由

切换语言时,使用 usePathname() 保留当前路径,只替换语言区段。请注意动态路由参数,它们需要针对新语言重新解析。

推荐的文件结构

Project Structure
my-nextjs-app/
├── middleware.ts              # Locale routing (project root!)
├── next.config.mjs
├── messages/
│   ├── en.json                # Source messages
│   ├── de.json
│   └── ja.json
├── src/
│   ├── i18n/
│   │   ├── request.ts         # Message loading config
│   │   └── routing.ts         # Locale definitions
│   └── app/
│       └── [locale]/
│           ├── layout.tsx     # Root locale layout
│           ├── page.tsx       # Home page
│           ├── error.tsx      # Localized error page
│           ├── not-found.tsx  # Localized 404
│           └── about/
│               └── page.tsx
└── package.json

立即试用 i18n Agent

将翻译文件拖放到此处

JSON, YAML, PO, XML, CSV, Markdown, Properties

或点击选择文件

目标语言

无需注册即时估价

使用 next-intl-localechain 实现语言回退

如果 pt-BR 等区域语言缺少翻译键,next-intl 会直接跳到默认语言,而不会先检查父语言 pt。

Terminal
npm install next-intl-localechain
Configuration
import { withLocaleChain } from 'next-intl-localechain';

export default withLocaleChain({
  fallbacks: {
    'pt-BR': ['pt', 'en'],
    'zh-Hant-HK': ['zh-Hant', 'zh', 'en'],
  },
  defaultLocale: 'en',
  loadMessages: (locale) => import(`./messages/${locale}.json`),
});

查看语言回退指南,了解受支持框架的完整列表和 75 条内置回退链。 Learn more →

常见问题