'Shorthand for union of all properties of type
Context: I'm making function that accepting const
asserted argument:
function langs<T extends Partial<Record<Lang, LangDict>>>(dict: T & Record<keyof T, UnionToIntersection<UnConst<T[keyof T]>>>) {
return () => dict[lang() as keyof T];
}
So I will able to see missing properties in my i18n dictionary:
const t = langs({
ru: {
title: (name) => `Привет ${name}`,
placeholder: 'Введите текст',
},
en: {
title: (name) => `Hello, ${name}`,
placeholder: 'Write text',
},
} as const);
I need to create union of all types that are properties of parent type:
type Lang = 'en' | 'ru' | 'de' | 'zh' | ...etc
type Dict = Partial<Record<Lang, LangDict>>;
type UnConst<T> = { [P in keyof T]:
T[P] extends string ? string : T[P] extends ((...args: string[]) => string) ? ((...args: string[]) => string) : UnConst<T[P]>
};
type Intersection = UnConst<Dict['ru']> & UnConst<Dict['en']> & UnConst<Dict['zh']> // ...a lot of types here
But I don't want to handle in manually. As I see, UnConst<Dict[Lang]>
is not the same as UnConst<Dict['ru']> & UnConst<Dict['en']> & UnConst<Dict['zh']> & ...
Is there shorthand to get same type as: Intersection
without manual intersecting all keys?
Full example: https://tsplay.dev/w65jEW
Solution 1:[1]
The crux of the problem:
UnConst<T[Lang]>
is actually
UnConst<A | Union | Here>
What you actually want is this:
UnConst<A> | UnConst<Union> | UnConst<Here>
because if you have this union, you can turn it into an intersection with this type:
// legendary holy grail of typescript developers
type UnionToIntersection<U> = (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never;
which would result in the desired
UnConst<A> & UnConst<Union> & UnConst<Here>
So how do we do this? We can use distributive conditional types. Let's define a type that takes a dictionary:
type AllLangs<T extends Partial<Record<Lang, LangDict>, ...
Then we store Lang
in a generic named L
so we can use it later in the distributive conditional:
type AllLangs<T extends Partial<Record<Lang, LangDict>>, L extends Lang = Lang> = ...
Finally, we do the magic:
type AllLangs<T extends Partial<Record<Lang, LangDict>>, L extends Lang = Lang> = L extends L ? UnConst<T[L]> : never;
But this is just the union, so we wrap that in UnionToIntersection
to finish the job:
type AllLangs<T extends Partial<Record<Lang, LangDict>>, L extends Lang = Lang> = UnionToIntersection<L extends L ? UnConst<T[L]> : never>;
You can read a little more about distributive conditional types here.
After we have our type that creates the desired result, we just need to use it:
export function langsAuto<T extends Partial<Record<Lang, LangDict>>>(dict: T & Record<keyof T, AllLangs<T>>) {
return () => dict[lang() as keyof T];
}
And as you can see in the playground, I think it works just like the manual one:
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | hittingonme |