Skip to content

设置基于语言环境的路由

更喜欢看视频?

为了为你的应用支持的每种语言使用唯一的路径名,next-intl 可用于处理以下路由设置:

  1. 基于前缀的路由(例如 /en/about
  2. 基于域名的路由(例如 en.example.com/about

在任一情况下,next-intl 通过使用顶层的 [locale] 动态段 与 App Router 集成,可用于提供不同语言的内容。

初始设置

要开始使用基于语言环境的路由,我们将设置以下文件:

src/i18n/routing.ts

我们将使用 routing.ts 作为定义路由配置的集中位置:

src/i18n/routing.ts
import {defineRouting} from 'next-intl/routing';
 
export const routing = defineRouting({
  // 支持的所有语言环境列表
  locales: ['en', 'de'],
 
  // 当没有匹配语言环境时使用
  defaultLocale: 'en'
});

根据需求,您可能以后想自定义路由配置 —— 但先完成设置。

src/proxy.ts

一旦路由配置就绪,我们可以用它来设置代理:

src/proxy.ts
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
 
export default createMiddleware(routing);
 
export const config = {
  // 匹配所有路径名,除了:
  // - 以 `/api`、`/trpc`、`/_next` 或 `/_vercel` 开头的路径
  // - 含点号的路径(例如 `favicon.ico`)
  matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
};

注意: 在 Next.js 16 之前,proxy.ts 被称为 middleware.ts

如何匹配包含点号的路径名,例如 /users/jane.doe

如果路径名中预期会有点号,可以通过显式条目进行匹配:

src/proxy.ts
// ...
 
export const config = {
  matcher: [
    // 匹配所有路径名,除了:
    // - 以 `/api`、`/trpc`、`/_next` 或 `/_vercel` 开头的路径
    // - 含点号的路径(例如 `favicon.ico`)
    '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
 
    // 匹配所有位于 `{/:locale}/users` 下的路径名
    '/([\\w-]+)?/users/(.+)'
  ]
};

这将匹配例如 /users/jane.doe,也可选搭配语言环境前缀。

src/i18n/navigation.ts

此外,我们可以利用路由配置来设置导航 API:

src/i18n/navigation.ts
import {createNavigation} from 'next-intl/navigation';
import {routing} from './routing';
 
// Next.js 导航 API 的轻量封装,考虑了路由配置
export const {Link, redirect, usePathname, useRouter, getPathname} =
  createNavigation(routing);

src/i18n/request.ts

现在,我们可以在请求配置中读取匹配到的语言环境:

src/i18n/request.ts
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';
 
export default getRequestConfig(async ({requestLocale}) => {
  // 通常对应 `[locale]` 段
  const requested = await requestLocale;
  const locale = hasLocale(routing.locales, requested)
    ? requested
    : routing.defaultLocale;
 
  return {
    locale
    // ...
  };
});

src/app/[locale]/layout.tsx

完成设置,我们将所有现有布局和页面移动到 [locale] 段:

src
└── app
    └── [locale]
        ├── layout.tsx
        ├── page.tsx
        └── ...

匹配到的 locale 现在可通过 [locale] 参数访问:

app/[locale]/layout.tsx
import {NextIntlClientProvider, hasLocale} from 'next-intl';
import {notFound} from 'next/navigation';
import {routing} from '@/i18n/routing';
 
type Props = {
  children: React.ReactNode;
  params: Promise<{locale: string}>;
};
 
export default async function LocaleLayout({children, params}: Props) {
  // 确保传入的 `locale` 有效
  const {locale} = await params;
  if (!hasLocale(routing.locales, locale)) {
    notFound();
  }
 
  // ...
}

就是这么简单!从这里开始,你可以根据具体需求配置路由

如果遇到问题,可以查看App Router 示例,了解一个可运行的示例应用。

静态渲染

使用基于语言环境的路由时,当在服务器组件中使用像 useTranslations 这类 API,next-intl 目前会选择动态渲染。我们计划未来移除这一限制,但作为临时方案,next-intl 提供了一个临时 API 来启用静态渲染。

添加 generateStaticParams

由于我们使用动态路由段 [locale],需要使用 generateStaticParams,以便在构建时渲染路由。

根据需求,你可以在布局或页面中添加 generateStaticParams

  1. 布局:为该布局下所有页面开启静态渲染(例如 app/[locale]/layout.tsx
  2. 单页:为特定页面开启静态渲染(例如 app/[locale]/page.tsx

示例:

import {routing} from '@/i18n/routing';
 
export function generateStaticParams() {
  return routing.locales.map((locale) => ({locale}));
}
我需要从 generateStaticParams 返回所有语言环境吗?

如果你只希望构建时渲染部分语言环境,或不渲染任何语言环境,可以选择性地返回需要的(详见 Next.js 文档中的静态渲染)。

在所有相关布局和页面中添加 setRequestLocale

next-intl 提供了一个 API,可用于将布局和页面通过 params 接收到的语言环境传递给该请求中渲染的所有服务器组件。

app/[locale]/layout.tsx
import {setRequestLocale} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {notFound} from 'next/navigation';
import {routing} from '@/i18n/routing';
 
type Props = {
  children: React.ReactNode;
  params: Promise<{locale: string}>;
};
 
export default async function LocaleLayout({children, params}: Props) {
  const {locale} = await params;
  if (!hasLocale(routing.locales, locale)) {
    notFound();
  }
 
  // 启用静态渲染
  setRequestLocale(locale);
 
  return (
    // ...
  );
}
app/[locale]/page.tsx
import {use} from 'react';
import {setRequestLocale} from 'next-intl/server';
import {useTranslations} from 'next-intl';
 
export default function IndexPage({params}) {
  const {locale} = use(params);
 
  // 启用静态渲染
  setRequestLocale(locale);
 
  // 一旦设置请求语言环境后,你可以调用 `next-intl` 的钩子
  const t = useTranslations('IndexPage');
 
  return (
    // ...
  );
}

请注意:

  1. 传递给 setRequestLocale 的语言环境应经过验证(例如,在你的根布局中)。
  2. 你需要在希望启用静态渲染的每个页面和布局中调用此函数,因为 Next.js 可能独立渲染布局和页面。
  3. 需要在调用 useTranslationsgetMessagesnext-intl 函数之前调用 setRequestLocale
setRequestLocale 是如何工作的?

next-intl 使用 cache() 创建了一个可变存储,用于保存当前语言环境。调用 setRequestLocale 时,当前语言环境会写入该存储,从而让所有需要语言环境的 API 访问它。

请注意,该存储是请求作用域的,因此不会影响在异步处理期间可能并行处理的其他请求。

为何需要此 API?

Next.js 目前没有提供 API 让服务器组件中任意位置读取路由参数如 locale(详见 vercel/next.js#58862)。而 localenext-intl 所有 API 的基础,通过整个组件树传递该参数显得不够方便。

因此,next-intl 使用其中间件,在传入请求中添加 x-next-intl-locale 头,存储协商出的语言环境。此方式可以通过 headers().get('x-next-intl-locale') 在任意位置读取语言环境。

但调用 headers 会使路由进入动态渲染。

通过使用 setRequestLocale,可以将布局和页面中通过 params 获得的语言环境传递给 next-intl。所有 next-intl API 均会从该值读取语言环境,启用静态渲染。

在元数据中使用 locale 参数

除了页面的渲染外,页面元数据也需要满足静态渲染条件。

为此,可以将 Next.js 通过 params 传递的 locale 转发给 next-intl 的可等待函数

page.tsx
import {getTranslations} from 'next-intl/server';
 
export async function generateMetadata({params}) {
  const {locale} = await params;
  const t = await getTranslations({locale, namespace: 'Metadata'});
 
  return {
    title: t('title')
  };
}