设置基于语言环境的路由
更喜欢看视频?
为了为你的应用支持的每种语言使用唯一的路径名,next-intl 可用于处理以下路由设置:
- 基于前缀的路由(例如
/en/about) - 基于域名的路由(例如
en.example.com/about)
在任一情况下,next-intl 通过使用顶层的 [locale] 动态段 与 App Router 集成,可用于提供不同语言的内容。
初始设置
要开始使用基于语言环境的路由,我们将设置以下文件:
src/i18n/routing.ts
我们将使用 routing.ts 作为定义路由配置的集中位置:
import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting({
// 支持的所有语言环境列表
locales: ['en', 'de'],
// 当没有匹配语言环境时使用
defaultLocale: 'en'
});根据需求,您可能以后想自定义路由配置 —— 但先完成设置。
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?
如果路径名中预期会有点号,可以通过显式条目进行匹配:
// ...
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:
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
现在,我们可以在请求配置中读取匹配到的语言环境:
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] 参数访问:
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:
- 布局:为该布局下所有页面开启静态渲染(例如
app/[locale]/layout.tsx) - 单页:为特定页面开启静态渲染(例如
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 接收到的语言环境传递给该请求中渲染的所有服务器组件。
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 (
// ...
);
}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 (
// ...
);
}请注意:
- 传递给
setRequestLocale的语言环境应经过验证(例如,在你的根布局中)。 - 你需要在希望启用静态渲染的每个页面和布局中调用此函数,因为 Next.js 可能独立渲染布局和页面。
- 需要在调用
useTranslations或getMessages等next-intl函数之前调用setRequestLocale。
setRequestLocale 是如何工作的?
next-intl 使用 cache() 创建了一个可变存储,用于保存当前语言环境。调用 setRequestLocale 时,当前语言环境会写入该存储,从而让所有需要语言环境的 API 访问它。
请注意,该存储是请求作用域的,因此不会影响在异步处理期间可能并行处理的其他请求。
为何需要此 API?
Next.js 目前没有提供 API 让服务器组件中任意位置读取路由参数如 locale(详见 vercel/next.js#58862)。而 locale 是 next-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 的可等待函数。
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')
};
}