Reut Schremer
Reut Schremer

Reputation: 368

typescript: How to define type as any part of enum?

I'm trying to create a translation module using typescript.

I want to define the languages as an enum parameter to create-text function, as like:


export enum Language {
    He = "he",
    En = "en",
}
const { createI18n, createI18nText } = createTextFunctions(Language);
const firstExample = createI18nText({
    he: {
        firstText: "שלום",
        sc: {
            hello: "שלום שוב"
        }
    },
    en: {
        firstText: "hello",
        sc: {
            hello: "hello again"
        }
    }
})
export const i18n = createI18n({
    welcome: firstExample,

})

But my problem is that because the languages are are passed as params to a typescript function and the function infers the types, typescript is not alarming anything. I can create text with non-existing language and it will pass it,like createI18nText({ ar:{ hi : "hi" }}).

My text functions are these:

export type Languages = { [key: string]: string };
export const createTextFunctions = (languages: LanguagesD) => {

    type I18nText<T extends object> = {
        [k in keyof typeof languages]: T;
    }

    const createI18n = <T extends { [key: string]: I18nText<any>; }>(i18n: T) => {
        return i18n;
    };

    const createI18nText = <T extends object>(text: I18nText<T>) => {
        return text;
    }

    return {
        createI18n,
        createI18nText
    }
}

So the code is running and doing whatever it needs to do, but I'm losing type control.

I do prefer to have my enum values lower-cased, so its an issue too. If this is the solution so I'll take it, but if there is any way to pass an enum-param and to run by its values it would be great.

Upvotes: 2

Views: 268

Answers (1)

Baboo
Baboo

Reputation: 4278

You can make createTextFunctions use a generic. You will be able to customise the keys as you want when you create the text function :

// *L* must be a union of strings.
const createTextFunctions = <L extends string>() => {

    type I18nText<T extends object> = Record<L, T>;
    type I18n = Record<string, I18nText<any>>;

    const createI18n = <T extends I18n>(i18n: T): T => {
        return i18n;
    };

    const createI18nText = <T extends object>(text: I18nText<T>): I18nText<T> => {
        return text;
    }

    return {
        createI18n,
        createI18nText
    }
}

Then specify the Language as a union of strings:

type Language = "en" | "he";

And create/use the text functions:

const { createI18n, createI18nText } = createTextFunctions<Language>();

const firstExample = createI18nText({
  he: {
    firstText: "שלום",
    sc: {
      hello: "שלום שוב"
    }
  },
  // If the key *en* is missing, typescript will complain.
  en: {
    firstText: "hello",
    sc: {
      hello: "hello again"
    }
  },
  // If we add the key *us*, typescript will complain.
})

export const i18n = createI18n({
  welcome: firstExample,
})

IMO union types are more comfortable to use than enums.

Upvotes: 1

Related Questions