Let’s say I have a type to denote a language code, for example:
type LangCodeUpper = 'EN' | 'DE' | 'ES';
type LangCodeLower = Lowercase<LangCodeUpper>;
The problem I’m trying to solve is the following:
const lang: LangCodeUpper = 'EN';
const lowerLang = lang.toLowerCase(); //this is `string`, while I want `LangCodeLower`
So far, I’ve got:
type LangCodeUpperDecl = 'EN' | 'DE' | 'ES';
type LangCodeLowerDecl = Lowercase<LangCodeUpper>;
type LanguageCaseImpl<T> = Omit<T, 'toLowerCase' | 'toUpperCase'> & {
toUpperCase?: () => LangCodeUpperDecl;
toLowerCase?: () => LangCodeLowerDecl;
};
type LangCodeUpper = LanguageCaseImpl<LangCodeUpperDecl>;
type LangCodeLower = LanguageCaseImpl<LangCodeLowerDecl>;
Which kind of works, since this successfully assigns the correct type when calling one of the two methods. But it also introduces new problems, namely:
function a(lang: LangCodeUpper) {
return this.title[lang]; //TS2538: Type 'LangCodeUpper' cannot be used as an index type.
}
The obvious solution is to introduce some kind of wrapper object, that would hold the value and provide methods like toCodeUpper
/toCodeLower
. Yet I would much rather stick with a basic type – is there a better way to override the methods for these types?