You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复next-intl中useTranslations的链式命名空间类型推导问题

问题核心原因

你之前的类型定义没有将useTranslations传入的命名空间对应的子字典类型,传递给返回的翻译函数,翻译函数始终校验的是完整字典的所有路径,所以传入子路径"three"时,会匹配完整字典的一级key(只有"one"),自然抛出类型错误。

解决方案

需要新增两个工具类型:

  1. DeepKeyOf<T>:递归生成目标对象所有合法属性路径的联合类型
  2. Get<T, Path>:根据传入的路径字符串,返回对象对应位置的属性类型

再调整useTranslations的类型定义,将命名空间对应的子字典类型,作为泛型传递给返回的翻译函数即可。

完整修改后代码
import type { ReactNode } from 'react';
import dict from "./locales/en"
// 假设 useNextTranslations 是 next-intl 原生导出的方法
import { useTranslations as useNextTranslations } from 'next-intl';

type Dict = typeof dict;

// 递归生成对象所有合法路径的联合类型
type DeepKeyOf<T> = T extends object 
  ? {
      [K in keyof T & string]: K | `${K}.${DeepKeyOf<T[K]>}`
    }[keyof T & string]
  : never;

// 根据路径字符串提取对象对应属性的类型
type Get<T, Path extends string> = Path extends `${infer K}.${infer Rest}`
  ? K extends keyof T
    ? Get<T[K], Rest>
    : never
  : Path extends keyof T
  ? T[Path]
  : never;

// 翻译函数的类型,泛型 D 为当前需要匹配的字典类型
interface UseTranslationsReturn<D> {
  <S extends DeepKeyOf<D>>(
    key: S,
    values?: Record<
      string,
      | string
      | number
      | boolean
      | Date
      | ((children: ReactNode) => ReactNode)
      | null
      | undefined
    >
  ): string
}

// useTranslations 主函数的类型
interface UseTranslationsProps {
  <S extends DeepKeyOf<Dict>>(namespace?: S): UseTranslationsReturn<Get<Dict, S>>
}

export const useTranslations: UseTranslationsProps = (namespace) => useNextTranslations(namespace)
效果验证
// 场景1:不传命名空间
const t1 = useTranslations()
const val1 = t1("one.two.three") // 类型合法,返回 "3"
const val2 = t1("one.two.foo") // 类型合法,返回 "bar"
t1("xxx") // 类型报错,提示无此路径

// 场景2:传入命名空间
const t2 = useTranslations("one.two")
const val3 = t2("three") // 类型合法,返回 "3"
const val4 = t2("foo") // 类型合法,返回 "bar"
t2("one") // 类型报错,提示无此key

内容的提问来源于stack exchange,提问作者cnotethegr8

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 18:15:01