本文最后更新于:2022年10月8日 早上
场景
吾辈有一些项目需要使用 i18next 来处理国际化,但是使用 typescript 需要有类型定义,所以之前在 joplin-utils 项目中维护和使用。昨天做了很多重构,现在已经分离出来并作为公共 npm 包发布。如果有人感兴趣,可以尝试一下。
GitHub
简介
i18next 的 typescript 类型定义生成器,可以从多个语言翻译 json 文件中生成类型定义,支持嵌套对象与参数。
使用
这个 cli 本身国际化配置的类型定义生成也是由 cli 完成的(自举)
1
| i18next-dts-gen --dirs src/i18n # 扫描这个目录下的 json 文件并生成 index.d.ts 类型定义
|
详情
1 2 3 4 5 6 7 8 9
| $ i18next-dts-gen -h Usage: bin [options]
根据 json 生成 .d.ts 类型定义
Options: -i, --dirs <dirs...> 包含一或多个翻译文件的目录 -w, --watch 是否使用监视模式 -h, --help display help for command
|
代码
下面是在 nodejs 中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import i18next from 'i18next'
export enum LanguageEnum { En = 'en', ZhCN = 'zhCN', }
export class I18nextUtil< T extends Record< string, { params: [key: string] | [key: string, params: object] value: string } >, > { constructor() {}
async changeLang(lang: LanguageEnum) { await i18next.changeLanguage(lang) }
async init(resources: Record<LanguageEnum, object>, language: LanguageEnum) { await i18next.init({ lng: language, resources: Object.entries(resources).reduce((res, [k, v]) => { Reflect.set(res, k, { translation: v, }) return res }, {}), keySeparator: false, }) }
t<K extends keyof T>(...args: T[K]['params']): T[K]['value'] { return i18next.t(args[0], args[1]) } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { TranslateType } from '../i18n' import osLocale from 'os-locale' import { I18nextUtil, LanguageEnum } from '../util/I18nextUtil'
export async function getLanguage(): Promise<LanguageEnum> { const language = await osLocale()
const map: Record<string, LanguageEnum> = { 'zh-CN': LanguageEnum.ZhCN, 'en-US': LanguageEnum.En, } return map[language] || LanguageEnum.En }
export const i18n = new I18nextUtil<TranslateType>()
|
1 2 3 4 5
| async function main() { await i18n.init({ en, zhCN }, await getLanguage()) console.log(i18n.t('hello', { name: 'liuli' })) }
|
或者在浏览器中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function getLanguage() { return navigator.language.startsWith('zh') ? LanguageEnum.ZhCN : LanguageEnum.En }
export const App: React.FC<AppProps> = () => { useMount(async () => { await i18n.init({ en, zhCN }, getLanguage()) })
return <div /> }
|
当然,如果你需要在其他环境中使用,应该仅需实现对应的 getLanguage
函数即可。
技巧
提示
导航
搜索和替换
动机
为什么已经有了很多第三方的类型定义生成器,甚至最新版 i18next 官方已经推出了 typescript 解决方案,吾辈还要写这个呢?
简而言之,都不完善。
先从 i18next 官方的解决方案说起,它是将 json 文件替换为 ts 文件,但不能支持参数和嵌套对象。
注:最新版似乎利用了 typescript 4.2 的递归类型和模板字符串类型来保证类型安全,但这实际上是不怎么好用的。另外只有 react-i18next 是可用的。
再来说 i18next-typescript 这个第三方库,几乎能满足吾辈的需求了,除了一点:支持对象参数。还有像是 Jack 菊苣的 i18n-codegen,代码设计上非常优雅,但同样的,不支持 react 之外的生态。
另外,就吾辈而言,认为使用生成器生成简单的类型要比从类型系统上支持这种功能更加容易,也更加合理。
设计
FAQ
是否支持 i18next 的全部特性?
否,这里支持的仅为 i18next 的一个子集。