next-intl 4.0
2025年3月12日 · 作者:Jan Amann经过一年的功能开发,本次发布重点在于简化 API 表面,同时保持 next-intl 的核心架构。随着许多改进已经在之前的小版本中发布,本次更新引入了多项增强功能,将提升您的开发体验,使国际化工作更加流畅。
next-intl@4.0 的新内容如下:
- 改进的增强类型
- 严格类型的 locale
- 严格类型的 ICU 参数
- GDPR 合规
- 现代化的构建输出
- 改进的
NextIntlClientProvider继承 - 针对
domains更严格的配置 - 为即将到来的 Next.js 特性做准备
在升级之前,请务必查看下面列出的其他变更。
改进的增强类型
在 next-intl@3.20 中添加了类型安全的 Formats 后,显然需要一个新的 API 来集中注册增强类型。
在 next-intl@4.0 中,Messages 和 Formats 现在可以注册到一个作用于 next-intl 并且不再影响全局作用域的单一类型下:
// global.ts
import {formats} from '@/i18n/request';
import en from './messages/en.json';
declare module 'next-intl' {
interface AppConfig {
Messages: typeof en;
Formats: typeof formats;
}
}请参阅更新后的 TypeScript 增强 指南。
严格类型的 locale
基于新的类型增强机制,next-intl@4.0 现允许您在整个应用中严格类型化 locale:
// global.ts
import {routing} from '@/i18n/routing';
declare module 'next-intl' {
interface AppConfig {
// ...
Locale: (typeof routing.locales)[number];
}
}这样,像 useLocale() 或 <Link /> 这类返回或接收 locale 的 API 会自动拾取您的应用专属 Locale 类型,提升整个应用的类型安全性。
为简化基于字符串的 locale 的缩小类型,添加了 hasLocale 函数。例如,它可用于 i18n/request.ts 中来返回有效的 locale:
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,
messages: (await import(`../../messages/${locale}.json`)).default
};
});此外,您也可以在应用代码中导入 Locale 类型,以确保传递 locale 参数时的类型安全:
import {Locale} from 'next-intl';
async function getPosts(locale: Locale) {
// ...
}请注意,严格类型化 Locale 是可选的,您可以根据需要使用,以为应用添加额外的防护措施。
严格类型的 ICU 参数
您的应用可以具备多大的类型安全性?
为了将类型安全延伸至 next-intl 的最后一角,我发现了由 Marco Schumacher 完全用类型编写的 ICU 解析器。Marco 友好地发布了他的实现以供 next-intl 使用,我仅在此基础上添加了对丰富标签的支持。
看看示例:
// "Hello {name}"
t('message', {});
// ^? {name: string}
// "It's {today, date, long}"
t('message', {});
// ^? {today: Date}
// "Page {page, number} out of {total, number}"
t('message', {});
// ^? {page: number, total: number}
// "You have {count, plural, =0 {no followers yet} one {one follower} other {# followers}}."
t('message', {});
// ^? {count: number}
// "Country: {country, select, US {United States} CA {Canada} other {Other}}"
t('message', {});
// ^? {country: 'US' | 'CA' | (string & {})}
// "Please refer to the <link>guidelines</link>."
t.rich('message', {});
// ^? {link: (chunks: ReactNode) => ReactNode}有了这种类型推断,您现在可以在 IDE 中获得有关给定 ICU 消息可用参数的自动补全建议,并提前发现潜在错误。
这也解决了我最喜欢吐槽的一个问题:
t('followers', {count: 30000});// ✖️ 会是: "30000 followers"
"{count} followers"
// ✅ 正确: "30,000 followers"
"{count, number} followers"由于 TypeScript 当前的限制,此功能暂时是可选启用。请参阅严格参数文档了解如何开启。
GDPR 合规
为符合现行 GDPR 规章,如果您正在使用支持基于 locale 路由的 next-intl 中间件,以下更改与您相关:
- locale cookie 现在默认为会话 cookie,浏览器关闭时过期。
- locale cookie 只有在用户切换到与
accept-language头部不匹配的 locale 时才会设置。
如果您想延长 cookie 过期时间,例如为告知用户使用 cookie 或您的应用不受 GDPR 约束,可使用 maxAge 属性:
// i18n/routing.tsx
import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting({
// ...
localeCookie: {
// 一年后过期
maxAge: 60 * 60 * 24 * 365
}
});由于 cookie 现在仅在 locale 切换后可用,请务必不要总依赖其存在。例如,若您需要在路由处理器中访问用户的 locale,可通过搜索参数传递 locale(例如 /api/posts/12?locale=en)作为可靠方案。
此更改还意味着禁用 cookie 现需在路由配置中设置 localeCookie: false。之前,localeDetection: false 模糊地也禁止了 cookie 设置,但因最近新增了独立 localeCookie 选项,应改用后者。
详情参考locale cookie文档。
现代化的构建输出
next-intl 的构建输出现已现代化,利用以下优化:
- 仅支持 ESM: 为实现更好的 tree-shaking 并与现代 JavaScript 生态对齐,
next-intl现仅发布为 ESM。唯一例外是next-intl/plugin由于next.config.js仍广泛使用,提供 CommonJS 和 ESM 两种版本。 - 现代 JSX 转换: React 的 peer 依赖已升级至 v17,以采用更高效的现代 JSX 转换。
- 现代语法: 语法编译目标为 Browserslist
defaults查询,即 “>0.5%, last 2 versions, Firefox ESR, not dead”——这是被认为现代应用合理的基础支持范围。
如果您用 Jest 或 Vitest 测试 next-intl,请参考新的测试文档。
这些更改使 next-intl 的包体积减少了约 7%(PR #1470)。
改进的 NextIntlClientProvider 继承
之前,NextIntlClientProvider 只谨慎地继承了来自 i18n/request.ts 的一小部分配置。
为提升入门体验,默认情况下,provider 现在还继承:
因此,如果您之前手动传递了这些属性,现在可以从 NextIntlClientProvider 中移除:
<NextIntlClientProvider
- messages={messages}
- formats={formats}
>
...
</NextIntlClientProvider>若您不希望继承这些属性并保持先前行为,可以选择禁用:
<NextIntlClientProvider
// 不向客户端传递这些
messages={null}
formats={null}
>
...
</NextIntlClientProvider>如今,NextIntlClientProvider 继承了所有配置,只有错误处理函数例外。由于函数不可序列化,无法穿越服务端/客户端边界。不过,替代方案正在规划中。
为更轻松地在客户端使用错误处理函数,NextIntlClientProvider 现支持嵌套使用,并继承父 Provider 的配置(PR #1413)。
针对 domains 更严格的配置
到目前为止,当结合使用 domains 和 localePrefix: 'as-needed' 时,next-intl 为避免在组件中读取请求的当前主机做出了一些权衡。
现在通过引入两个新约束,next-intl 可完全避免这些权衡:
- 每个 locale 只能对应一个唯一域名
- 每个域名必须明确指定其所支持的
locales
这简化并使模型更直观,非常适合这一热门用例。
若您之前跨多个域使用同一个 locale,现在必须更具体——通常通过为基础语言引入区域变体。您也可以根据需要自定义前缀。
示例:
import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting({
locales: ['sv-SE', 'en-SE', 'no-NO', 'en-NO'],
defaultLocale: 'en-SE',
localePrefix: {
mode: 'as-needed',
prefixes: {
'en-SE': '/en',
'en-NO': '/en'
}
},
domains: [
{
domain: 'example.se',
defaultLocale: 'sv-SE',
locales: ['sv-SE', 'en-SE']
},
{
domain: 'example.no',
defaultLocale: 'no-NO',
locales: ['no-NO', 'en-NO']
}
]
});这将创建如下结构:
example.se:sv-SEexample.se/en:en-SEexample.no:no-NOexample.no/en:en-NO
更多内容请参考更新后的 domains 文档。
为即将到来的 Next.js 特性做准备
为确保 next-intl 在未来稳定运行,我研究了 Next.js 即将推出的特性如 ppr、dynamicIO 和 rootParams 对 next-intl 的影响。
由此带来了三个小变更:
- 如果应用中尚未有包裹所有使用
next-intl的客户端组件的NextIntlClientProvider,现在必须添加一个(详见 PR #1541)。 - 如果您在客户端组件中使用
format.relativeTime,可能需要显式提供now参数(详见 PR #1536)。 - 若使用基于 locale 的路由,确保已升级至引入的
await requestLocale(next-intl@3.22)。以前弃用的locale参数将来会在rootParams实现后用作边缘案例(详见 PR #1625)。
虽然这些 Next.js 特性仍在开发中,可能发生变化,但这些更改在我看来非常合理,理想情况下是适应即将发布功能的全部必要调整。
我尤其期待 rootParams 的发布,因为这似乎终于能解决缺失的一环,使基于 locale 路由的应用无需如 setRequestLocale 之类的变通即可支持静态渲染。期待不久能带来更多分享!
其他变更
- 从
useMessages和getMessages返回类型安全的消息(见 PR #1489) - 要求
getRequestConfig必须返回 locale(见 PR #1486) - 支持部分声明
pathnames以便捷使用(见 PR #1743) - 禁止传递
null、undefined或boolean作为 ICU 参数(见 PR #1561) - 对使用 TypeScript 的项目,最低 TypeScript 版本提升至 5(见 PR #1481)
- 使用
localePrefix: 'always'时,为子页面也返回x-default备用链接,并更新中间件匹配建议为/((?!api|_next|_vercel|.*\\..*).*)(见 PR #1720) - 移除弃用的 API(见 PR #1479)
- 移除弃用的 API 第二部分(见 PR #1482)
立即升级
为顺利升级,请先升级到最新的 v3.x 版本并检查弃用警告。
随后,您可以运行以下命令升级:
npm install next-intl@4我很期待听到您对 next-intl@4.0 的使用体验!欢迎加入 讨论区 分享交流。
感谢!
由衷感谢所有帮助塑造 next-intl 如今模样的人。
特别感谢 Crowdin,作为 next-intl 的赞助合作伙伴,使我得以定期投入该项目并将其提供为免费且开源的库。
——Jan
(本文基于 4.0 发布候选版的初次公告进行了更新)
附注:您知道吗,learn.next-intl.dev 即将上线?
Let’s keep in touch: