next-intl 3.22:逐步前进
2024年10月21日 · 作者 Jan Amann在过去几个月中,已经发布了多个小版本,每个版本都为 next-intl 带来了一些渐进式的改进。虽然每个版本本身都是对现有功能的提升,但它们同时也是向着更统一、更简洁的 API 形态迈进的更大进程的一部分。
今天,发布了另一个小版本,标志着这一进程的最终步骤:next-intl@3.22。
尽管此版本完全向后兼容,但它包含了一些现有 API 的现代替代方案。因此,现在是一个很好的机会来回顾这些变化,并酌情考虑进行迁移。
近期改进:
defineRouting:类型安全、集中的路由配置(v3.18引入)i18n/request.ts:精简的文件组织(v3.19引入)await requestLocale:为 Next.js 15 做准备(v3.22引入)createNavigation:简化的导航 API(v3.22引入)setRequestLocale:静态渲染(v3.22标记为稳定)defaultTranslationValues:被弃用,建议采用用户空间模式(v3.22引入)
让我们更详细地看看这些变化。
defineRouting
之前,中间件和导航 API 之间共享的配置需要格外小心以保持同步。随着 defineRouting 的引入,这些配置现在可以以类型安全的方式集中管理:
import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting({
locales: ['en-US', 'en-GB'],
defaultLocale: 'en-US',
localePrefix: {
mode: 'always',
prefixes: {
'en-US': '/us',
'en-GB': '/uk'
}
},
pathnames: {
'/': '/',
'/organization': {
'en-US': '/organization',
'en-GB': '/organisation'
}
}
});这个 routing 配置通常定义在像 i18n/routing.ts 这样的集中位置,可以用于以前需要单独配置的地方:
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
export default createMiddleware(routing);
// ...如果你在 v3.22 之前使用过 defineRouting,并且向 createMiddleware 传递了中间件选项作为第二个参数,现在可以改为传递给 defineRouting。
文档已持续更新以反映这些变更,并建议直接在 i18n/routing.ts 中创建导航 API 以简化流程。当然,如果你更喜欢单独维护导航 API,这也是完全可以的。
i18n/request.ts
如果你使用基于语言环境的路由,通常会有两个 next-intl 配置文件:
- 路由配置(通过
defineRouting定义) - 请求配置(通过
getRequestConfig定义)
虽然历史上(2)建议放在 i18n.ts,但默认位置现在已改为 i18n/request.ts,以简化文件组织并更明确地表达此文件的用途:
└── src
└── i18n
├── routing.ts (1)
└── request.ts (2)因此,i18n/request.ts 现在被认为是新的默认位置,并在文档中广泛推荐。
如果你想使用自定义位置,可以在 next.config.mjs 中进行配置:
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin('./somewhere/else/request.ts');
// ...await requestLocale 在 getRequestConfig 中
Next.js 15 即将到来,带来了请求 API 的 异步化变化。为配合此变更,传递给 getRequestConfig 的 locale 已被替代为 requestLocale,并伴有一些细微差别:
- 现在须对
requestLocale进行await - 结果可能为
undefined,因此需提供回退 - 需要在
getRequestConfig中返回locale
+ import {routing} from './i18n/routing';
export default getRequestConfig(async ({
- locale
+ requestLocale
}) => {
+ // 这通常对应于 `[locale]` 路由段
+ let locale = await requestLocale;
- // 验证传入的 `locale` 参数是否合法
- if (!routing.locales.includes(locale as any)) notFound();
+ // 确保传入的 locale 有效
+ if (!locale || !routing.locales.includes(locale as any)) {
+ locale = routing.defaultLocale;
+ }
return {
+ locale,
// ...
};
});虽然略显啰嗦,但这次改动允许你在中间件不能提供 locale(例如全局国家选择页面 / 或全局 404 页面)的边缘情况下返回回退 locale。
另外,建议在这里返回有效的 locale 以应对 [locale] 路由段中遇到未知值的情况。但你也可以选择在一个集中位置(如你的根布局)进行校验并有条件地调用 notFound()。
createNavigation
新增的 createNavigation 函数取代了以下旧 API:
createSharedPathnamesNavigationcreateLocalizedPathnamesNavigation
此新函数是现有导航功能的重新实现,统一了两种使用场景下的 API,同时修复了之前 API 的一些瑕疵。此外,该实现针对 Next.js 15 进行了更新,能无警告运行。
用法
import {createNavigation} from 'next-intl/navigation';
import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting(/* ... */);
export const {Link, redirect, usePathname, useRouter} =
createNavigation(routing);迁移到 createNavigation
createNavigation 通常可作为无缝替代,但可能需做以下调整:
createNavigation期望接收完整的路由配置。理想做法是通过defineRouting定义并将结果传入。唯一例外是构建时未知 locales 情况。- 如果使用过
createLocalizedPathnamesNavigation并且已在Link组件的href属性上进行组合,则不应再传递泛型Pathname类型参数。
- ComponentProps<typeof Link<Pathname>>
+ ComponentProps<typeof Link>- 如果使用过
redirect,现在必须显式提供 locale(即使只是当前 locale)。此前直接传递的 href(字符串或对象)需用对象包裹,并赋值给href属性。此改动是为了配合 Next.js 15 的准备。
// 获取当前 locale
// ... 普通组件内:
const locale = useLocale();
// ... 异步组件内:
const locale = await getLocale();- redirect('/about')
+ redirect({href: '/about', locale})
- redirect({pathname: '/users/[id]', params: {id: 2}})
+ redirect({href: {pathname: '/users/[id]', params: {id: 2}}, locale})- 如果你使用过
getPathname并且之前手动添加了 locale 前缀,现在不要这样做了 ——getPathname会根据你的路由策略自动处理。
- '/'+ locale + getPathname(/* ... */)
+ getPathname(/* ... */);- 如果你同时使用
localePrefix: 'as-needed'和domains,且调用了getPathname,现在需要传入domain参数(请参阅 特殊情况:domains与localePrefix: 'as-needed'配合使用)。
setRequestLocale 标记为稳定
如果你依赖于静态渲染,可能之前用过 unstable_setRequestLocale API。这个函数现在被标记为稳定,因为它在可预见的未来仍会继续使用。
- import {unstable_setRequestLocale} from 'next-intl/server';
+ import {setRequestLocale} from 'next-intl/server';大约一年前,我在 Next.js 仓库开启了讨论 #58862,试图探讨 Next.js 是否能提供一种在服务端组件中访问用户 locale 的方法,而不会牺牲易用性或产生渲染问题。尽管该问题颇受关注,目前排在过去一年最受欢迎讨论第 2 名,但 Next.js 团队迄今尚未对此发表回应。据我理解,这确实不是一个容易解决的问题,但如果有机会,我非常愿意参与合作。
尽管我仍然乐观地认为未来某天能让 setRequestLocale API 变得多余,但“unstable”(不稳定)前缀已经不再合适——该 API 自推出以来已被证明稳定可靠。
defaultTranslationValues(弃用)
defaultTranslationValues 允许你在整个应用中共享全局值用于消息。最常见的例子是共享的富文本元素(例如 b: (chunks) => <b>{chunks}</b>)。
但随着时间推移,该功能暴露出了弊端:
因此,该功能将被弃用,文档现建议使用更好的替代方案:
import {useTranslations} from 'next-intl';
import RichText from '@/components/RichText';
function AboutPage() {
const t = useTranslations('AboutPage');
return <RichText>{(tags) => t.rich('description', tags)}</RichText>;
}接下来?
这些发布说明原计划作为下一个大版本的一部分发布。不过,我很高兴能将这些变化逐步推送而且不产生破坏性变更。
所有改进都源于与众多 next-intl 用户和贡献者的对话。我非常荣幸能收到大家如此多的反馈,并确保通过一个精简、统一的包能够满足广泛的国际化用例。同时,衷心感谢所有参与测试该版本预发行版的朋友们。
有了这些变化,用户可以在 v3 系列中按自己的节奏升级到现代 API。话虽如此,4.0 版本已经在筹备中,主要目标是清理废弃功能,确保 next-intl 保持极简主义。
——Jan
Let’s keep in touch: