I'm writing a getLocale(obj, key, locale)
function that returns a key with a specific locale from an object, or returns a fallback locale variant.
I'd like to have type checking for this function, but I can't figure out how to merge the key
and locale
arguments to create the keyof obj
:
My data:
const data = {
name_nl: 'Naam',
name_en: 'Name',
}
The function:
const allowedLangs = ['nl', 'en'] as const;
function getLocale<T extends Record<K, any>, K extends keyof any>(
data: T,
key: K,
locale: typeof allowedLangs[number]
): T[K] | 'not-found' {
return data[`${key}_${locale}`] || data[`${key}_en`] || 'not-found';
}
Calling the function:
getLocale(data, 'name', 'nl'); // Naam
TypeScript, however, complains that Property 'name' is missing in type
, which is correct, since K
is not directly a keyof T
in my function, but the combination of locale
and key
are.
Is there a way to merge these arguments?
CodePudding user response:
You should change allowed langs to be an enum. The way you are inferring the type is not correct
locale: typeof allowedLangs[number]
Should in fact be
locale: ArrElement<typeof allowedLangs>
The problem you are seeing though is on this line
key: K,
There isn't any better typing you can do than: key: string
CodePudding user response:
type Lang = 'nl' | 'en'
type Data<K extends string> = Record<`${K}_${Lang}`, any>
function getLocale<K extends string>(
data: Data<K>,
key: K,
locale: Lang
): [K] | 'not-found' {
return data[`${key}_${locale}`] || data[`${key}_en`] || 'not-found';
}
const l = getLocale({
name_nl: 'Naam',
name_en: 'Name',
}, 'name', 'nl');
Try out on ts playground.
CodePudding user response:
You can modify your function signature to solve this problem.
function getLocale<
T extends Record<`${K}_${typeof allowedLangs[number]}`, any>,
K extends string
>(
data: T,
key: K,
locale: typeof allowedLangs[number]
) {
return data[`${key}_${locale}`] || data[`${key}_en`] || 'not-found';
}
Instead of using keyof any
for K
, just constrain it to string
. We can use a template literal string type in the Record
of T
to constrain the key to start with K
and end with typeof allowedLangs[number]
.
You can just remove the return type of the function, so TypeScript can automatically infer it. The inferred return type will look like this:
T[`${K}_nl` | `${K}_en`] | T[`${K}_en`] | "not-found"
You now have complete validation when calling the function.
getLocale(data, 'name', 'nl');
getLocale(data, 'abc', 'nl'); // Error
getLocale(data, 'name', 'de'); // Error