0

could someone give me a hint on how to restrict the valid arguments of the function getMessage:

type Translations = {
    readonly [key: string]: Localize;
}

interface Localize {
    readonly "en": string,
    readonly "de"?: string,
    readonly "fr"?: string
}

const translationsObj: Translations = {
    "keyOne": { "en":"one", "de":"eins" },
    "keyTwo": { "en":"two", "de":"zwei" }
};

type TranslationKeys = keyof typeof translationsObj; // = string | number

function getMessage(key: TranslationKeys) {
    return translationsObj[key]["de"];
}

const msg1 = getMessage("keyOne"); // OK
const msg2 = getMessage("notAPropOfTranslationsObj"); // should be marked as error

Only the properties of the object translationsObj should be allowed to be passed.

Roland

2 Answers 2

2

If you were to use Typescript 4.9 beta, you could use the satisfies operator; it does exactly what you want.

type Translations = {
    readonly [key: string]: Localize;
}

interface Localize {
    readonly "en": string,
    readonly "de"?: string,
    readonly "fr"?: string
}

const translationsObj = {
    "keyOne": { "en":"one", "de":"eins" },
    "keyTwo": { "en":"two", "de":"zwei" }
} satisfies Translations;

type TranslationKeys = keyof typeof translationsObj; // = string | number

function getMessage(key: TranslationKeys) {
    return translationsObj[key]["de"];
}

const msg1 = getMessage("keyOne"); // OK
const msg2 = getMessage("notAPropOfTranslationsObj"); // error

Playground link

Sign up to request clarification or add additional context in comments.

2 Comments

Ooh, good to know and very useful. I've had to work around this so much, this would make things a lot easier.
That works great with Typescript 4.9 beta, that's what I was looking for, thanks Akxe.
2

Get rid of the Translations type. By defining it as a map, you're basically saying its keys are unbounded and can be any string.

Instead, just define it in terms of what keys are available and create a record out of that.

type TranslationKeys = "keyOne" | "keyTwo";

const translationsObj: Record<TranslationKeys, Localize> = {
    "keyOne": { "en":"one", "de":"eins" },
    "keyTwo": { "en":"two", "de":"zwei" }
};

function getMessage(key: TranslationKeys) {
    return translationsObj[key]["de"];
}

const msg1 = getMessage("keyOne"); // OK
const msg2 = getMessage("notAPropOfTranslationsObj"); // error

playground


On the other hand, if you have a lot of keys you map out, then you might want to create a helper function to get the right types.

const translations = <T extends { [key: string]: Localize }>(t: T) => t as Record<keyof T, Localize>;

const translationsObj = translations({
    "keyOne": { "en":"one", "de":"eins" },
    "keyTwo": { "en":"two", "de":"zwei" }
});

type TranslationKeys = keyof typeof translationsObj;

playground

4 Comments

Thank you for your reply Jeff. So I have to write the keys twice, right? There will be many more properties in the object translationsObj. I wanted to avoid that.
In that case, you'll need a helper function to help build the type.
I'll have a look at it too, thanks!
Good job with that helper function

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.