导航 APIs
想看视频教程吗?
next-intl 提供了对 Next.js 导航 APIs(如 <Link /> 和 useRouter)的轻量包装,能够在后台自动处理用户的语言和路径。
要创建这些 API,可以通过传入你的 routing 配置来调用 createNavigation 函数:
import {createNavigation} from 'next-intl/navigation';
import {routing} from './routing';
export const {Link, redirect, usePathname, useRouter, getPathname} =
createNavigation(routing);该函数通常在像 src/i18n/navigation.ts 这样的集中模块中调用,以方便组件中访问导航 API。
如果构建时不知道所有语言怎么办?
如果你的应用在运行时允许添加或删除语言,可以调用 createNavigation 时不传 locales 参数,从而允许运行时遇到的任意字符串作为有效语言。在这种情况下,你将不会使用 defineRouting 函数。
import {createNavigation} from 'next-intl/navigation';
export const {Link, redirect, usePathname, useRouter, getPathname} =
createNavigation({
// ... 可能有其他路由配置,但不包含 `locales` ...
});不过请注意,middleware 中的 locales 参数仍是必需的。如果你需要在运行时获取可用语言,可以为 middleware 动态按请求提供路由配置。
APIs
创建的导航 API 是对 Next.js 同名 API 的薄封装,并且大多数保持相同的函数签名。你的路由配置和用户语言会自动融合其中。
如果你在路由配置中使用了 pathnames 设置,那么传递给 href 参数的内部路径名将是严格类型化的,并会针对指定语言进行本地化。
Link
该组件封装了 next/link 并根据需要对路径名进行本地化。
import {Link} from '@/i18n/navigation';
// 当用户在 `/en` 时,链接会指向 `/en/about`
<Link href="/about">关于</Link>
// 也可以通过 `query` 添加搜索参数
<Link href={{pathname: "/users", query: {sortBy: 'name'}}}>用户</Link>
// 你可以通过 `locale` 覆盖语言切换到其他语言
// (这会在 <a> 标签上设置 `hreflang` 属性)
<Link href="/" locale="de">切换到德语</Link>根据是否使用了 pathnames 设置,动态参数可以按以下两种方式传递:
// 1. 最终字符串(不使用 `pathnames` 时)
<Link href="/users/12">苏珊</Link>
// 2. 对象(使用 `pathnames` 时)
<Link href={{
pathname: '/users/[userId]',
params: {userId: '5'}
}}>
苏珊
</Link>如何渲染一个导航链接?
Next.js 的 useSelectedLayoutSegment hook 允许你在父布局中检测某个子段是否处于激活状态。因为它返回的是一个内部路径名,所以你可以把它与传递给 Link 的 href 进行匹配。
'use client';
import {useSelectedLayoutSegment} from 'next/navigation';
import {ComponentProps} from 'react';
import {Link} from '@/i18n/navigation';
export default function NavigationLink({
href,
...rest
}: ComponentProps<typeof Link>) {
const selectedLayoutSegment = useSelectedLayoutSegment();
const pathname = selectedLayoutSegment ? `/${selectedLayoutSegment}` : '/';
const isActive = pathname === href;
return (
<Link
aria-current={isActive ? 'page' : undefined}
href={href}
style={{fontWeight: isActive ? 'bold' : 'normal'}}
{...rest}
/>
);
}<nav>
<NavigationLink href="/">{t('home')}</NavigationLink>
<NavigationLink href="/about">{t('about')}</NavigationLink>
<NavigationLink href="/blog">{t('blog')}</NavigationLink>
</nav>另请参见 Next.js 关于创建激活链接组件的文档。
如何组合包装组件中的 href 属性?
如果你需要创建一个接收 href 属性并内部传给 Link 的组件,可以用 ComponentProps 类型组合 Link 的属性:
import {ComponentProps} from 'react';
import {Link} from '@/i18n/navigation';
type Props = ComponentProps<typeof Link> & {
color: 'blue' | 'red';
};
export default function StyledLink({color, href, ...rest}: Props) {
return <Link href={href} style={{color}} {...rest} />;
}如果你使用了 pathnames 选项,包装组件的 href 属性将变成基于路由配置的严格类型。
为什么使用 locale 属性时 <Link /> 总是会添加语言前缀?
当你提供 locale 属性(通常是为了切换目标页面语言)时,会发现链接的 href 总是会包含语言前缀,即使你使用的是 localePrefix 中非 always 的设置。
示例:
如果你使用 localePrefix: 'as-needed' 且默认语言是 en,则以下链接的 href 仍然会是 /en/about:
// 链接到 `/en/about`
<Link href="/about" locale="en">
关于
</Link>原因是可能需要先更新语言 cookie,才能访问不带前缀的 /about 路由。带前缀的路径会帮助完成这一步,然后重定向到无前缀路由。此行为是必须的,因为链接可能在页面 hydration 之前被点击,客户端代码还未有机会更新 cookie。
如果你想避免此行为,可以使用 useRouter 来切换语言,该方法在导航到目标页面前可在客户端更新 cookie。
使用 pathnames 设置时,如何链接到未知路由?
此情况下导航 API 具有严格的类型检查,只允许 pathnames 配置中声明的路由。如果你需要在某些情况下链接到未知路由,可以逐一禁用类型检查:
// @ts-expect-error
<Link href="/unknown">...</Link>未知路由会原样传递,但在绝对路径时会附加相应语言前缀。
本地化链接的预加载是如何工作的?
next-intl 的 <Link /> 继承了 next/link 的默认预加载行为。
唯一的例外是当你设置了 locale 属性时。此时链接不会被预加载,因为预加载请求会过早覆盖语言 cookie。
useRouter
如果你需要在事件处理函数中程序化导航,next-intl 提供了包装了 Next.js useRouter 的便利 API,会相应地本地化路径名。
'use client';
import {useRouter} from '@/i18n/navigation';
const router = useRouter();
// 当用户在 `/en` 时,router 会跳转到 `/en/about`
router.push('/about');
// 也可以通过 `query` 添加搜索参数
router.push({
pathname: '/users',
query: {sortBy: 'name'}
});
// 你可以通过 `locale` 覆盖语言切换到其他语言
router.replace('/about', {locale: 'de'});根据是否使用了 pathnames 设置,动态参数可以这样传递:
// 1. 最终字符串(不使用 `pathnames` 时)
router.push('/users/12');
// 2. 对象(使用 `pathnames` 时)
router.push({
pathname: '/users/[userId]',
params: {userId: '5'}
});如何更改当前页面的语言?
结合 usePathname 和 useRouter,你可以通过保持相同路径并覆盖 locale 来编程更改当前页面语言。
根据是否使用 pathnames 设置,你可能还需要传递 params 以解析内部路径名。
'use client';
import {usePathname, useRouter} from '@/i18n/navigation';
import {useParams} from 'next/navigation';
const pathname = usePathname();
const router = useRouter();
// 不使用 `pathnames`: 直接传递当前 pathname
router.replace(pathname, {locale: 'de'});
// 使用 `pathnames`: 额外传递 params
const params = useParams();
router.replace(
// @ts-expect-error -- TypeScript 会验证仅允许给定 `pathname` 的已知 `params`,由于两者总是对应当前路由,可以跳过运行时检查
{pathname, params},
{locale: 'de'}
);了解更多:
usePathname
获取当前路径名(不含语言前缀)可以调用 usePathname。
'use client';
import {usePathname} from '@/i18n/navigation';
// 当用户在 `/en` 时,这里返回的是 `/`
const pathname = usePathname();如果你启用了 pathnames 设置,返回的路径名将对应一个内部路径模板(动态参数不会被替换成具体值)。
// 当用户在 `/de/über-uns` 时,返回 `/about`
const pathname = usePathname();
// 当用户在 `/de/neuigkeiten/produktneuheit` 时,返回 `/news/[articleSlug]`
const pathname = usePathname();redirect
如果你想中断渲染并重定向到其他页面,可以调用 redirect 函数。该函数封装了 Next.js 的 redirect,并按需本地化路径名。
注意:即使只是传递当前语言,也必须提供 locale 属性。
import {redirect} from '@/i18n/navigation';
// 重定向到 `/en/login`
redirect({href: '/login', locale: 'en'});
// 也可通过 `query` 添加搜索参数
redirect({href: '/users', query: {sortBy: 'name'}, locale: 'en'});根据是否使用了 pathnames 设置,动态参数可以这么传递:
// 1. 最终字符串(不使用 `pathnames`)
redirect({href: '/users/12', locale: 'en'});
// 2. 对象(使用 `pathnames`)
redirect({
href: {
pathname: '/users/[userId]',
params: {userId: '5'}
},
locale: 'en'
});如果你使用的是非 always 的 localePrefix 设置,可以通过将 forcePrefix 设为 true 强制添加语言前缀。这在切换用户语言并需要先更新语言 cookie时非常有用:
// 无视你的 `localePrefix` 设置,首次会跳转到 `/en/about` 以更新语言 cookie
redirect({href: '/about', locale: 'en', forcePrefix: true});permanentRedirect
也同样支持。
为什么调用 redirect 后,TypeScript 不会正确缩小类型?
TypeScript 目前存在一个限制,导致在调用 redirect 后,类型缩小以及不可达代码检测无法正确生效:
import {redirect} from '@/i18n/navigation';
function UserProfile({userId}: {userId?: string}) {
if (!userId) {
redirect({href: '/login', locale: 'en'});
}
// 这里 userId 本应被收窄为 string,但 TypeScript 不能正确分析
}解决此限制的方式是将调用 redirect 函数作为 return:
if (!userId) {
return redirect({href: '/login', locale: 'en'});
}
// ✅ 这里的 userId 已被收窄为 stringgetPathname
当你需要基于某个语言构建特定路径名时,可以调用 getPathname 函数。
import {getPathname} from '@/i18n/navigation';
// 返回 `/en/about`
const pathname = getPathname({
locale: 'en',
href: '/about'
});
// 也可以通过 `query` 添加搜索参数
const pathname = getPathname({
locale: 'en',
href: {
pathname: '/users',
query: {sortBy: 'name'}
}
});根据是否使用了 pathnames 设置,动态参数可以这样传递:
// 1. 最终字符串(不使用 `pathnames`)
const pathname = getPathname({
locale: 'en',
href: '/users/12'
});
// 2. 对象(使用 `pathnames`)
const pathname = getPathname({
locale: 'en',
href: {
pathname: '/users/[userId]',
params: {userId: '5'}
}
});使用场景示例: