TypeScript 增强
想看视频讲解吗?
next-intl 开箱即用,与 TypeScript 无缝集成,无需额外设置。
不过,您也可以选择性地提供补充定义来增强 next-intl 使用的类型,从而提升整个应用的代码自动补全和类型安全。
import {routing} from '@/i18n/routing';
import {formats} from '@/i18n/request';
import messages from './messages/en.json';
declare module 'next-intl' {
interface AppConfig {
Locale: (typeof routing.locales)[number];
Messages: typeof messages;
Formats: typeof formats;
}
}类型增强支持:
Locale
增强 Locale 类型会影响所有从 next-intl 中获取或传入 locale 的 API:
import {useLocale} from 'next-intl';
// ✅ 'en' | 'de'
const locale = useLocale();import {Link} from '@/i18n/routing';
// ✅ 通过验证
<Link href="/" locale="en" />;此外,next-intl 提供了一个 Locale 类型,用于传递 locale 参数时使用。
要启用此验证,可按如下方式调整 AppConfig:
import {routing} from '@/i18n/routing';
declare module 'next-intl' {
interface AppConfig {
// ...
Locale: (typeof routing.locales)[number];
}
}Messages
消息内容可严格定义类型,确保使用有效的键。
{
"About": {
"title": "Hello"
}
}function About() {
// ✅ 有效的命名空间
const t = useTranslations('About');
// ✖️ 不存在的消息键
t('description');
// ✅ 有效的消息键
t('title');
}要启用此验证,可按如下方式调整 AppConfig:
import messages from './messages/en.json';
declare module 'next-intl' {
interface AppConfig {
// ...
Messages: typeof messages;
}
}您可以自由定义接口,但如果本地有消息文件,自动根据默认 locale 的消息生成类型会很有帮助。
这会影响类型检查的性能吗?
虽然消息文件的大小会影响 TypeScript 编译的时间,但增强 Messages 的开销通常是合理快速的。
以下是一个包含 340 条消息的示例项目的性能基准:
- 无消息类型增强: ~2.20秒
- 类型安全的键: ~2.82秒
- 类型安全的参数: ~2.85秒
以上数据来源于 MacBook Pro 2019(Intel)。
若在较大项目中遇到性能问题,可考虑:
- 只在持续集成管线中使用消息的类型增强作为安全措施
- 将项目拆分为 monorepo 中的多个包,以便分别处理每个包的消息
类型安全的参数
除了严格限定消息键外,还能确保消息参数的类型安全:
{
"UserProfile": {
"title": "Hello {firstName}"
}
}function UserProfile({user}) {
const t = useTranslations('UserProfile');
// ✖️ 缺少参数
t('title');
// ✅ 参数存在
t('title', {firstName: user.firstName});
}TypeScript 目前存在一个限制,它将导入的 JSON 模块的值推断为宽泛类型(如 string),而非实际值。为此,next-intl 可以为您赋值给 AppConfig 的消息生成配套的 .d.json.ts 声明文件作为临时解决方案。
用法:
- 在
tsconfig.json中添加对 JSON 类型声明的支持:
{
"compilerOptions": {
// ...
"allowArbitraryExtensions": true
}
}- 在 Next.js 配置中设置
createMessagesDeclaration:
import {createNextIntlPlugin} from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin({
experimental: {
// 提供赋值给 `AppConfig` 的消息文件路径
createMessagesDeclaration: './messages/en.json'
}
// ...
});
// ...完成上述配置后,运行 next dev、next build 或 next typegen 时,会在 messages 目录生成一个新的声明文件:
messages/en.json
+ messages/en.d.json.ts该声明文件为您导入并赋值给 AppConfig 的 JSON 消息提供精确类型,从而支持消息参数的类型安全。
为了保持代码仓库整洁,建议在 Git 中忽略此文件:
messages/*.d.json.ts欢迎为 TypeScript#32063 点赞,期待未来能去除这一权宜之计。
Formats
如果使用全局格式,可以严格限定传递给 format.dateTime、format.number 和 format.list 的格式名称。
function Component() {
const format = useFormatter();
// ✖️ 未知格式字符串
format.dateTime(new Date(), 'unknown');
// ✅ 有效格式
format.dateTime(new Date(), 'short');
// ✅ 有效格式
format.number(2, 'precise');
// ✅ 有效格式
format.list(['HTML', 'CSS', 'JavaScript'], 'enumeration');
}要启用此验证,可从请求配置中导出您使用的 formats:
import {Formats} from 'next-intl';
export const formats = {
dateTime: {
short: {
day: 'numeric',
month: 'short',
year: 'numeric'
}
},
number: {
precise: {
maximumFractionDigits: 5
}
},
list: {
enumeration: {
style: 'long',
type: 'conjunction'
}
}
} satisfies Formats;
// ...然后,将 formats 包含到您的 AppConfig 中:
import {formats} from '@/i18n/request';
declare module 'next-intl' {
interface AppConfig {
// ...
Formats: typeof formats;
}
}排查问题
如果遇到问题,请检查:
- 接口名称是否正确为
AppConfig。 - 类型声明文件是否包含在
tsconfig.json中。 - 编辑器是否已加载最新类型,必要时重启编辑器。