英文:
Typescript: Omitting translated properties
问题
我有可以翻译的接口 - 例如,它们具有 nameDe
和 nameFr
属性。目前我正在尝试构建一个函数,该函数仅返回用户语言中具有 name
属性的对象。
到目前为止,我有以下内容 - 它适用于已翻译的属性,但不适用于“未翻译”的属性:
type TranslatedObject<T> = {
[K in keyof T as K extends `${infer Name}De` ? `${Name}` : never]: T[K extends `${infer Name}De` ? K : never];
};
interface Car {
nameDe: string,
nameFr: string,
price: number
}
const carWithUserLang: TranslatedObject<Car> = {
name: 'Make Model Trim',
price: 50000 // --> 类型 '{ name: string; price: number; }' 无法赋值给类型 'TranslatedObject<Car>'。
}
如何使其正常运行?
Stackblitz: https://stackblitz.com/edit/typescript-h7ghdn?file=index.ts&view=editor
英文:
I have interfaces that can be translated - they have the nameDe
and nameFr
properties for example. Currently I'm trying to build a function that returns the object with name
only in the users language.
This is what I have so far - it works for the translated properties but not the "non-translated":
type TranslatedObject<T> = {
[K in keyof T as K extends `${infer Name}De` ? `${Name}` : never]: T[K extends `${infer Name}De` ? K : never];
};
interface Car {
nameDe: string,
nameFr: string,
price: number
}
const carWithUserLang: TranslatedObject<Car> = {
name: 'Make Model Trim',
price: 50000 // --> Type '{ name: string; price: number; }' is not assignable to type 'TranslatedObject<Car>'.
}
How can I make this run?
Stackblitz: https://stackblitz.com/edit/typescript-h7ghdn?file=index.ts&view=editor
答案1
得分: 2
我的假设是您希望 TranslatedObject<T>
剥离键名末尾的 "De"
和 "Fr"
,如果存在的话,否则保持不变。 如果是这样,那么这纯粹是一个 键重映 操作,不改变属性值类型。 就像这样:
type TranslatedObject<T> = {
[K in keyof T as K extends `${infer Name}${"De" | "Fr"}` ? Name : K]: T[K];
};
它使用 条件类型推断 在 模板文字类型 内部剥离任何 "De"
或 "Fr"
后缀。 检查 K extends `${infer Name}${"De" | "Fr"}`
尝试将 K
与以某个任意字符串开头且以 "De" | "Fr"
结尾的内容匹配;如果成功,则推断 Name
是那个任意的初始字符串。 所以结果的键类型在解析成功时会评估为 Name
,如果失败则为 K
。
这导致了您期望的类型:
interface Car {
nameDe: string,
nameFr: string,
price: number
}
type TranslatedCar = TranslatedObject<Car>;
/* type TranslatedCar = {
name: string;
price: number;
} */
英文:
My assumption is that you want TranslatedObject<T>
to strip "De"
and "Fr"
off of the ends of key names, if they exist, and otherwise leave them alone. If so then this is purely a key remapping operation that doesn't change the property value types. Like this:
type TranslatedObject<T> = {
[K in keyof T as K extends `${infer Name}${"De" | "Fr"}` ? Name : K]: T[K];
};
It uses a conditional type inference within a template literal type to strip off any "De"
or "Fr"
suffix. The check K extends `${infer Name}${"De" | "Fr"}`
tries to match K
to something that starts with some arbitrary string and ends in "De" | "Fr"
; if it succeeds, then Name
is inferred to be that arbitrary initial string. So then the resulting key type evaluates to Name
if the parsing succeeded, or K
if it failed.
That results in the type you were expecting:
interface Car {
nameDe: string,
nameFr: string,
price: number
}
type TranslatedCar = TranslatedObject<Car>;
/* type TranslatedCar = {
name: string;
price: number;
} */
Note that there are some interesting edge cases, like what happens if your two keys have different value types:
type Weird = TranslatedObject<{
oopsDe: string;
oopsFr: number;
other: boolean;
}>
/* type Weird = {
oops: string | number; // <-- becomes union
other: boolean;
} /
That might be what one would expect, or it might not. The point is that one should always thoroughly test against one's use cases.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论