banner
yyh

Hi, I'm yyh

github
x
email

Next-Intl 的 namespace 拆分与类型安全:可维护 i18n 的两件事

重点一:如何拆分 namespace#

官方文档强调 “按 locale 组织消息 + nested 结构”,但没有给出多文件拆分的落地方式。可维护的拆分方式如下:

  1. 目录按 locale、文件按 feature
    messages/{locale}/{namespace}.json
    每个功能一个 namespace 文件,避免单文件膨胀,团队协作时也更容易控制冲突。

  2. 配置驱动加载
    i18n/config.ts 维护 localesnamespaces;请求入口一次性并行加载全部 namespace,再合并为完整 messages。官方文档提到 getRequestConfig 在请求周期内缓存执行,这一层只做一次加载与合并。

  3. 默认语言兜底合并
    默认语言补齐缺失项,既保证完整性,又保持 runtime 用法不变;getMessageFallback 可用于缺失提示。

一个推荐的加载形态如下:

// i18n/request.ts
async function loadMessages(locale: Locale) {
  const userMessages = await importNamespaces(locale);
  if (locale === defaultLocale) return userMessages;
  const baseMessages = await importNamespaces(defaultLocale);
  return deepmerge(baseMessages, userMessages);
}

重点二:如何提供类型检查#

官方文档给出类型安全的总体方向,但需要补上 “怎么把 key 变成类型” 的落地步骤。可维护的做法有两种:

方案 A:Messages 类型 + module augmentation(最通用)
用基准语言的 JSON 生成 Messages 类型,再扩展 next-intlAppConfig。这样 t("...") 只能使用真实存在的 key。

// types/i18n.d.ts
type CommonMessages = typeof import("../messages/en-US/common.json");
export type Messages = {
  common: CommonMessages;
  auth: typeof import("../messages/en-US/auth.json");
  chat: typeof import("../messages/en-US/chat.json");
};
// types/global.d.ts
declare module "next-intl" {
  interface AppConfig {
    Locale: Locale;
    Messages: Messages;
  }
}

方案 B:createMessagesDeclaration(官方实验方案)
createNextIntlPlugincreateMessagesDeclaration 可以基于样例消息文件生成严格声明,进一步强化参数类型安全。
官方也指出 JSON 推断存在限制,因此生成声明文件是一条更可靠的路径。

小结#

拆分 namespace 解决规模与协作问题,类型约束解决 key 漏洞;统一入口加载与兜底合并保证运行时成本一致。这两件事做到位,i18n 才能长期稳定扩展。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。