'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);

enter image description here

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:

Playground

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