2

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.

1
  • 1
    Does this work for you to limit the language names to what's passed in? If not, please elaborate on what's missing. Otherwise I can write up an answer when I get a chance. Commented Jul 8, 2021 at 15:08

1 Answer 1

1

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.

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

2 Comments

Thank you! It works, but I still prefer enum. @jcalz comment worked as well. Thanks for your effort!
Don't forget to mark the question as answered then ;)

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.