Gre*_*een 10 typescript i18next react-i18next
是否可以在react-i18next字典中键入检查现有键?因此,如果密钥不存在,TS 会在编译时警告您。
例子。
假设,我们有这本字典:
{
"footer": {
"copyright": "Some copyrights"
},
"header": {
"logo": "Logo",
"link": "Link",
},
}
Run Code Online (Sandbox Code Playgroud)
如果我提供不存在的密钥,TS 应该会爆炸:
const { t } = useTranslation();
<span> { t('footer.copyright') } </span> // this is OK, because footer.copyright exists
<span> { t('footer.logo') } </span> // TS BOOM!! there is no footer.logo in dictionary
Run Code Online (Sandbox Code Playgroud)
这种技术的正确名称是什么?我很确定我不是唯一一个要求这种行为的人。
它是react-i18next开箱即用的吗?是否有 APIreact-i18next以某种方式扩展库以启用它?我想避免创建包装函数。
for*_*d04 21
我们现在可以使用虚线字符串参数来深入访问字典键/对象路径:
t("footer"); // ? { copyright: "Some copyrights"; }
t("footer.copyright"); // ? "Some copyrights"
t("footer.logo"); // ? should trigger compile error
Run Code Online (Sandbox Code Playgroud)
让我们看看1.)翻译函数的合适返回类型t 2.)我们如何在不匹配的键参数上发出编译错误并提供 IntelliSense 3.)字符串插值示例。
// returns property value from object O given property path T, otherwise never
type GetDictValue<T extends string, O> =
T extends `${infer A}.${infer B}` ?
A extends keyof O ? GetDictValue<B, O[A]> : never
: T extends keyof O ? O[T] : never
function t<P extends string>(p: P): GetDictValue<P, typeof dict> { /* impl */ }
Run Code Online (Sandbox Code Playgroud)
在错误的键上触发编译错误可能就足够了:
// returns the same string literal T, if props match, else never
type CheckDictString<T extends string, O> =
T extends `${infer A}.${infer B}` ?
A extends keyof O ? `${A}.${Extract<CheckDictString<B, O[A]>, string>}` :never
: T extends keyof O ? T : never
function t<P extends string>(p: CheckDictString<P, typeof dict>)
: GetDictValue<P, typeof dict> { /* impl */ }
Run Code Online (Sandbox Code Playgroud)
如果您还需要IntelliSense,请继续阅读。以下类型将查询字典的所有可能的键路径排列,提供自动完成并协助不匹配键的错误提示:
// get all possible key paths
type DeepKeys<T> = T extends object ? {
[K in keyof T]-?: `${K & string}` | Concat<K & string, DeepKeys<T[K]>>
}[keyof T] : ""
// or: only get leaf and no intermediate key path
type DeepLeafKeys<T> = T extends object ?
{ [K in keyof T]-?: Concat<K & string, DeepKeys<T[K]>> }[keyof T] : "";
type Concat<K extends string, P extends string> =
`${K}${"" extends P ? "" : "."}${P}`
Run Code Online (Sandbox Code Playgroud)
function t<P extends DeepKeys<typeof dict>>(p: P) : GetDictValue<P, typeof dict>
{ /* impl */ }
type T1 = DeepKeys<typeof dict>
// "footer" | "header" | "footer.copyright" | "header.logo" | "header.link"
type T2 = DeepLeafKeys<typeof dict>
// "footer.copyright" | "header.logo" | "header.link"
Run Code Online (Sandbox Code Playgroud)
有关更多详细信息,请参阅Typescript:嵌套对象的深度 keyof。
由于组合复杂性和取决于字典对象的形状,您可能会遇到编译器递归深度限制。更轻量级的替代方案:根据当前输入为下一个关键路径增量提供 IntelliSense :
// T is the dictionary, S ist the next string part of the object property path
// If S does not match dict shape, return its next expected properties
type DeepKeys<T, S extends string> =
T extends object
? S extends `${infer I1}.${infer I2}`
? I1 extends keyof T
? `${I1}.${DeepKeys<T[I1], I2>}`
: keyof T & string
: S extends keyof T
? `${S}`
: keyof T & string
: ""
function t<S extends string>(p: DeepKeys<typeof dict, S>)
: GetDictValue<S, typeof dict> { /* impl */ }
t("f"); // error, suggests "footer"
t("footer"); // OK
t("footer."); // error, suggests "footer.copyright"
t("footer.copyright"); // OK
t("header.") // error, suggests "header.logo" | "header.link"
Run Code Online (Sandbox Code Playgroud)
// retrieves all variable placeholder names as tuple
type Keys<S extends string> = S extends '' ? [] :
S extends `${infer _}{{${infer B}}}${infer C}` ? [B, ...Keys<C>] : never
// substitutes placeholder variables with input values
type Interpolate<S extends string, I extends Record<Keys<S>[number], string>> =
S extends '' ? '' :
S extends `${infer A}{{${infer B}}}${infer C}` ?
`${A}${I[Extract<B, keyof I>]}${Interpolate<C, I>}`
: never
Run Code Online (Sandbox Code Playgroud)
例子:
type Dict = { "key": "yeah, {{what}} is {{how}}" }
type KeysDict = Keys<Dict["key"]> // type KeysDict = ["what", "how"]
type I1 = Interpolate<Dict["key"], { what: 'i18next', how: 'great' }>;
// type I1 = "yeah, i18next is great"
function t<
K extends keyof Dict,
I extends Record<Keys<Dict[K]>[number], string>
>(k: K, args: I): Interpolate<Dict[K], I> { /* impl */ }
const ret = t('key', { what: 'i18next', how: 'great' } as const);
// const ret: "yeah, i18next is great"
Run Code Online (Sandbox Code Playgroud)
注意:所有片段都可以结合使用react-i18next或独立使用。
( PRE TS 4.1 ) 强类型键在react-i18next以下情况下是不可能的有两个原因:
1.) TypeScript 无法评估动态或计算出的字符串表达式,例如'footer.copyright',因此footer和copyright可以被识别为翻译对象层次结构中的关键部分。
2.)useTranslation不会对您定义的字典/翻译强制执行类型约束。相反,函数t包含默认为 的泛型类型参数string,当未手动指定时。
这是使用Rest parameters/tuples的替代解决方案。
输入t函数:
type Dictionary = string | DictionaryObject;
type DictionaryObject = { [K: string]: Dictionary };
interface TypedTFunction<D extends Dictionary> {
<K extends keyof D>(args: K): D[K];
<K extends keyof D, K1 extends keyof D[K]>(...args: [K, K1]): D[K][K1];
<K extends keyof D, K1 extends keyof D[K], K2 extends keyof D[K][K1]>(
...args: [K, K1, K2]
): D[K][K1][K2];
// ... up to a reasonable key parameters length of your choice ...
}
Run Code Online (Sandbox Code Playgroud)
类型useTranslation挂钩:
import { useTranslation } from 'react-i18next';
type MyTranslations = {/* your concrete type*/}
// e.g. via const dict = {...}; export type MyTranslations = typeof dict
// import this hook in other modules instead of i18next useTranslation
export function useTypedTranslation(): { t: TypedTFunction<typeof dict> } {
const { t } = useTranslation();
// implementation goes here: join keys by dot (depends on your config)
// and delegate to lib t
return { t(...keys: string[]) { return t(keys.join(".")) } }
}
Run Code Online (Sandbox Code Playgroud)useTypedTranslation在其他模块中
导入:
import { useTypedTranslation } from "./useTypedTranslation"
const App = () => {
const { t } = useTypedTranslation()
return <div>{t("footer", "copyright")}</div>
}
Run Code Online (Sandbox Code Playgroud)
测试一下:
const res1 = t("footer"); // const res1: { "copyright": string;}
const res2 = t("footer", "copyright"); // const res2: string
const res3 = t("footer", "copyright", "lala"); // error, OK
const res4 = t("lala"); // error, OK
const res5 = t("footer", "lala"); // error, OK
Run Code Online (Sandbox Code Playgroud)
您可能会自动推断这些类型,而不是多个重载签名 ( Playground )。要知道,这些递归类型是不建议在生产的核心开发人员,直到TS 4.1。
React-i18next 现在对此有内置支持。我找不到官方文档,但源代码中有有用的注释。
假设您的翻译是在public/locales/[locale]/translation.json并且您的主要语言是英语:
// src/i18n-resources.d.ts
import 'react-i18next'
declare module 'react-i18next' {
export interface Resources {
translation: typeof import('../public/locales/en/translation.json')
}
}
Run Code Online (Sandbox Code Playgroud)
如果您使用多个翻译文件,您需要将它们全部添加到资源界面,由命名空间键入。
如果您从 json 文件导入翻译,请确保"resolveJsonModule": true在您的设置中进行设置tsconfig.json。
| 归档时间: |
|
| 查看次数: |
10002 次 |
| 最近记录: |