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

重点一：如何拆分 namespace

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

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

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

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

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

Copy
// 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-intl 的 AppConfig。这样 t("...") 只能使用真实存在的 key。

Copy
// 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");
};

Copy
// types/global.d.ts
declare module "next-intl" {
interface AppConfig {
Locale: Locale;
Messages: Messages;
}
}

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

小结

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