Home > Mobile >  Type checking on concatenated function arguments
Type checking on concatenated function arguments

Time:07-20

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:

Use Template Literal Types.

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 Recordof 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

Playground

  • Related