手写一个简单的 i18next.t 函数的类型推导

2025-03-01

在深度使用了一段时间的 i18next 后,我发现 i18next.t 这个函数类型推导机制非常强大,能够在写代码时提供便捷的翻译键来方便开发。所以,我决定深入研究一下它的类型推导机制。

一、i18next.t 类型使用简介

都知道,我们在使用 i18next 时,通常会这样定义一个资源类型:

typescript

然后,我们就可以在代码中使用类型安全的方式调用翻译键:

typescript

很神奇是不是,再看看他支持的调用方式:

typescript

这些 i18next 的代码,只要你按照文档的定义,在类型中总能得到想要的值,比如当你这样定义了 CustomTypeOptions 时,你调用的时候,就能看到实际的翻译是什么,正如代码编辑器提供的智能提示所示:

TypeScript 自从在 4.1 版本增加 模板字面量类型(Template Literal Types) 功能后,实现了这些字符串转换的神奇操作。所以,为了了解如何实现这种转换机制,有必要先自己尝试一次。

二、自行实现一个简单的 i18next.t 定义

首先,目前使用的 TypeScript 版本只需要支持模板字面量类型就可以了,我们需要的是一个最简单的转换 const nihao = i18next.t(“common:hello”)。为了实现这个转换,我需要考虑两个事情:

  1. 如何提取所有合法的输入参数类型
  2. 将这些类型返回出来一个合法的值

提取合法类型

先定义一下必要的类型:

typescript

然后,定义一下这个函数的大概形式,下面代码中的 ??? 代表目前的推导过程不清楚其类型格式,简而言之就是个占位符。

typescript

为了解析类似 Ns:Key 等字符,我们应该先写出类似的形式,下面就是使用 Template Literal Types 对该类型进行定义。

typescript

然后,函数的参数应该需要根据上面的格式进行约束和调整。

typescript

我们发现,现在需要拿到的是对应命名空间下的对象的所有键,也就是 keyof Resources[string],所以应该是这个:

typescript

然而,很遗憾,keyof Resources[FlatNamespace] 对应的类型是 neverkeyof 无法针对联合类型导出。所以我们需要换个思路,可以改用映射类型显式遍历每个命名空间。

typescript

所以,我们的 TFunction 应该是这样:

typescript

最后,TFunctionReturn 需要根据 Key 来找到具体的资源值类型。在这里,模板字符串配合 infer 可以导出相对应的字面量类型。

typescript

看看最终的代码是怎么样的:

typescript

三、总结

i18next.t 的类型系统的实现充分展示了 TypeScript 4.1+ 强大的类型编程能力,通过组合基础类型工具,可以构建出能精确描述复杂业务场景的类型系统。在实际开发中,这种类型安全的国际化方案可以显著提升代码质量,在编译阶段就能发现 90% 以上的键名错误,极大提升开发体验。

虽然上面的实现的功能并没有支持很多特性,但是已经实现了其最基础的根据命名空间取值的做法。至于其他功能,有空再写吧。