我有一个对象Locale
,该对象保存了我的应用的本地化版本。
export const Locale = {
EN: "en",
DE: "de",
EL: "el",
FR: "fr",
}
export const Locales = Object.values(Locale)
我还有一个代表API类型的对象,即Trip
export interface Trip {
id: number,
title: string,
description: string,
featured: boolean,
}
Trip
的某些字段是可翻译的(在这种情况下为标题,说明),这意味着在某些情况下,API会发送带有其语言环境的本地化字段。
例如:
{
"id": 1,
"title_el": "Greek Title",
"title_de": "German title",
"title_fr": "French Title",
"title_en": "English Title,
"description_en": "",
"description_el": "",
"description_de": "",
"description_fr": "",
"featured": true
}
我想做的是以某种方式生成一种新类型LocalizedTrip
,其中包含可本地化的字段以及所有不可本地化的字段。
我尝试利用Typescript的4.1 Key Remaps,但运气不佳
我创建了一个codeandbox来开始聚会: https://codesandbox.io/s/xenodochial-frog-dr6de?file=/src/types.ts
export const Locale = {
EN: "en",
DE: "de",
EL: "el",
FR: "fr",
}
export const Locales = Object.values(Locale)
type LocalizedType<T> = {
[K in keyof T as `[what here?]`]: () => T[K]
}
export interface Trip {
id: number,
title: string, //Maybe create another type for the localizable fields?
description: string,
featured: boolean,
}
type LocalizedTrip = LocalizedType<Trip>
const test: LocalizedTrip = {
}
test.title_en
test.title_de
答案 0 :(得分:4)
我认为您需要明确哪些键应该本地化。我的默认猜测是那些键不是symbol
且其值可分配给string
的属性:
type LocalizableProps<T> = Exclude<{
[K in keyof T]-?: T[K] extends string ? K : never }[keyof T]
, symbol>;
为Trip
生成此代码:
type TripStringProps = LocalizableProps<Trip>
// type TripStringProps = "title" | "description"
如果这不能捕获您的意图,则可以将LocalizableProps
更改为可以(或变得足够接近)的其他类型的函数。
具有与语言环境字符串的并集相对应的类型也有帮助。目前,Locale
的属性仅被视为string
,对于我们的目的而言太宽了。为了不丢失Locale
对象中特定的string literal值,我们应该使用类似const
assertion的东西:
export const Locale = {
EN: "en",
DE: "de",
EL: "el",
FR: "fr",
} as const;
type Locales = typeof Locale[keyof typeof Locale]
/* type Locales = "en" | "de" | "el" | "fr" */
现在,如果您更改Locale
对象,则编译器应注意并自动适应。
最后,我们可以定义LocalizedType<T, K>
,其中K
默认为LocalizableProps<T>
,但是您可以在必要时覆盖它:
type LocalizedType<T, K extends Exclude<keyof T, symbol> = LocalizableProps<T>> = {
[P in keyof T as (
P extends K ? `${P}_${Locales}` : P
)]: T[P]
}
这是一个非常简单的键映射:对于P
中的每个键keyof T
,如果P
是指定键K
之一,则使用并集{{1 }},它成为一个并集(例如,如果`${P}_${Locales}`
为P
,则"x"
将为${P}_{Locales}
。否则,保持不变。
让我们对其进行测试:
"x_en" | "x_de" | "x_el" | "x_fr"
看起来不错,让我们看看如果指定其他键会发生什么情况
type LocalizedTrip = LocalizedType<Trip>
/* type LocalizedTrip = {
id: number;
title_en: string;
title_de: string;
title_el: string;
title_fr: string;
description_en: string;
description_de: string;
description_el: string;
description_fr: string;
featured: boolean;
} */
答案 1 :(得分:0)
不是最易读的类型,但这是可行的。需要TS4.1。
用法:
type LocalizedTrip = LocalizedType<Trip, "title" | "description">;
// type LocalizedTrip = {
// title_en: string;
// title_de: string;
// title_el: string;
// title_fr: string;
// description_en: string;
// description_de: string;
// description_el: string;
// description_fr: string;
// id: number;
// featured: boolean;
// }
定义:
type LocalizedType<T, K> = {
[LK in keyof typeof Locale as 0]: {
[TK in Extract<keyof T, string>
as (TK extends K ? `${TK}_${(typeof Locale)[LK]}` : TK)]: T[TK]
}
}[0];
0
用作将内部对象组合在一起的外部键。 TK extends K ?
三元可确保仅对K
中指定的键进行本地化。
编辑:基于Jcalz的回答,我们可以摆脱0
:
type LocaleStr = (typeof Locale)[keyof typeof Locale];
type LocalizedType<T, K> = {
[TK in Extract<keyof T, string>
as (TK extends K ? `${TK}_${LocaleStr}` : TK)]: T[TK]
};