Skip to content
博客next-intl 4.0

next-intl 4.0

2025年3月12日 · 作者:Jan Amann

经过一年的功能开发,本次发布重点在于简化 API 表面,同时保持 next-intl 的核心架构。随着许多改进已经在之前的小版本中发布,本次更新引入了多项增强功能,将提升您的开发体验,使国际化工作更加流畅。

next-intl@4.0 的新内容如下:

  1. 改进的增强类型
  2. 严格类型的 locale
  3. 严格类型的 ICU 参数
  4. GDPR 合规
  5. 现代化的构建输出
  6. 改进的 NextIntlClientProvider 继承
  7. 针对 domains 更严格的配置
  8. 为即将到来的 Next.js 特性做准备

在升级之前,请务必查看下面列出的其他变更

改进的增强类型

next-intl@3.20 中添加了类型安全的 Formats 后,显然需要一个新的 API 来集中注册增强类型。

next-intl@4.0 中,MessagesFormats 现在可以注册到一个作用于 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 中间件,以下更改与您相关:

  1. locale cookie 现在默认为会话 cookie,浏览器关闭时过期。
  2. 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 的构建输出现已现代化,利用以下优化:

  1. 仅支持 ESM: 为实现更好的 tree-shaking 并与现代 JavaScript 生态对齐,next-intl 现仅发布为 ESM。唯一例外是 next-intl/plugin 由于 next.config.js 仍广泛使用,提供 CommonJS 和 ESM 两种版本。
  2. 现代 JSX 转换: React 的 peer 依赖已升级至 v17,以采用更高效的现代 JSX 转换。
  3. 现代语法: 语法编译目标为 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 更严格的配置

到目前为止,当结合使用 domainslocalePrefix: 'as-needed' 时,next-intl 为避免在组件中读取请求的当前主机做出了一些权衡

现在通过引入两个新约束,next-intl 可完全避免这些权衡:

  1. 每个 locale 只能对应一个唯一域名
  2. 每个域名必须明确指定其所支持的 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-SE
  • example.se/en: en-SE
  • example.no: no-NO
  • example.no/en: en-NO

更多内容请参考更新后的 domains 文档。

为即将到来的 Next.js 特性做准备

为确保 next-intl 在未来稳定运行,我研究了 Next.js 即将推出的特性如 pprdynamicIOrootParamsnext-intl 的影响。

由此带来了三个小变更:

  1. 如果应用中尚未有包裹所有使用 next-intl 的客户端组件的 NextIntlClientProvider,现在必须添加一个(详见 PR #1541)。
  2. 如果您在客户端组件中使用 format.relativeTime,可能需要显式提供 now 参数(详见 PR #1536)。
  3. 若使用基于 locale 的路由,确保已升级至引入的 await requestLocalenext-intl@3.22)。以前弃用的 locale 参数将来会在 rootParams 实现后用作边缘案例(详见 PR #1625)。

虽然这些 Next.js 特性仍在开发中,可能发生变化,但这些更改在我看来非常合理,理想情况下是适应即将发布功能的全部必要调整。

我尤其期待 rootParams 的发布,因为这似乎终于能解决缺失的一环,使基于 locale 路由的应用无需如 setRequestLocale 之类的变通即可支持静态渲染。期待不久能带来更多分享!

其他变更

  1. useMessagesgetMessages 返回类型安全的消息(见 PR #1489
  2. 要求 getRequestConfig 必须返回 locale(见 PR #1486
  3. 支持部分声明 pathnames 以便捷使用(见 PR #1743
  4. 禁止传递 nullundefinedboolean 作为 ICU 参数(见 PR #1561
  5. 对使用 TypeScript 的项目,最低 TypeScript 版本提升至 5(见 PR #1481
  6. 使用 localePrefix: 'always' 时,为子页面也返回 x-default 备用链接,并更新中间件匹配建议为 /((?!api|_next|_vercel|.*\\..*).*)(见 PR #1720
  7. 移除弃用的 API(见 PR #1479
  8. 移除弃用的 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: