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

如何解决typesafe-i18n动态调用类型化函数的TS类型错误?

解决typesafe-i18n动态翻译的TypeScript类型匹配问题

背景

我正在使用typesafe-i18n库,它会生成强类型的翻译信息与函数,简化后的示例如下:

export type MyTranslations = {
  Hello: (arg: { field: unknown}) => string
  Bye: (arg: { field: unknown, date: unknown}) => string
  Foo: (arg: { field: unknown}) => unknown
  Bar: (arg: { max: unknown}) => unknown,
  test: string // 示例:不是所有属性都必须是函数
}

const translations: MyTranslations = {
  Hello: (arg: { field: unknown}) => 'hello',
  Bye: (arg: { field: unknown, date: unknown}) => 'bye',
  Foo: (arg: { field: unknown}) => 'foo',
  Bar: (arg: { max: unknown}) => 'bar',
  test: ''
}

现有代码

准备代码

// 准备代码
interface MyParams {
  [index: string]: boolean | number | string | undefined
  field?: keyof MyTranslations
}

interface Result {
  transKey: keyof MyTranslations,
  params?: MyParams
}

const results: Result[] = [
  {
    transKey: 'Hello',
    params: {
      field: 'Bye'
    }
  },
  {
    transKey: 'Bar',
    params: {
      max: 'test'
    }
  }
] 

type PickByType<T, V> = {
  [P in keyof T as T[P] extends V | undefined ? P : never]: T[P]
}

翻译函数代码

function translate(results: Result[]) {
  results.forEach((result: Result) => {
      type A = PickByType<MyTranslations, Function>
      type C = keyof A
     
      if(result.params) {
        type T = typeof result.params
        type Req = Required<T>

        const req = result.params as Req
        
        const func = translations[result.transKey]
        type F = typeof func
        

        const f = translations as A
        f[result.transKey as C](req) // 此处报错

      }
  })
}

translate(results)

问题描述

报错信息:

Argument of type 'Required<MyParams>' is not assignable to parameter of type '{ field: unknown; } & { field: unknown; date: unknown; } & { field: unknown; } & { max: unknown; }'.
  Property 'date' is missing in type 'Required<MyParams>' but required in type '{ field: unknown; date: unknown; }'

原因是当前代码中Result的params类型过于宽泛,TypeScript会将所有翻译函数的参数类型合并为交叉类型,要求传入的参数同时满足所有函数的参数结构,但实际每个翻译键只需要对应自身的参数结构。

解决思路

步骤1:强关联翻译键与参数类型

先提取每个翻译函数的参数类型,再重新定义Result类型,让transKey和params强绑定:

// 提取翻译函数的参数类型
type TranslationFnParams<K extends keyof MyTranslations> = 
  MyTranslations[K] extends (...args: [infer P]) => any ? P : never;

// 定义强类型的Result联合类型
type Result = {
  [K in keyof MyTranslations]: {
    transKey: K;
    params?: TranslationFnParams<K>;
  }
}[keyof MyTranslations];

此时Result会自动生成每个翻译键对应正确参数的联合类型,比如{ transKey: 'Hello', params: { field: unknown } } | { transKey: 'Bye', params: { field: unknown; date: unknown } },编译期就能拦截参数不匹配的错误。

步骤2:调整翻译函数逻辑

利用TypeScript的类型推断,直接匹配每个翻译键对应的函数和参数:

function translate(results: Result[]) {
  results.forEach((result) => {
    const transFn = translations[result.transKey];
    // 仅处理函数类型的翻译键
    if (typeof transFn === 'function') {
      // 根据参数是否存在调用对应逻辑
      if (result.params !== undefined) {
        transFn(result.params);
      } else {
        // 针对无参数的翻译函数做类型断言
        (transFn as () => any)();
      }
    }
  });
}

translate(results);

补充说明

  • 调整后的Result类型会自动校验transKey和params的匹配关系,比如给Bar传field参数会直接报错;
  • 对于无需参数的翻译键(比如示例中的test),params会被自动标记为可选,调用时无需传入参数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 10:03:29