I'm using the i18next package to manage text translation in a React typescript project. I want guarantee that the string path I provide to the t
function actually exists, so I'm creating a type of possible paths and a wrapper function. The code (for the type) would look something like this:
const enTranslation = {
a: {
a1: "a1",
a2: "a2",
},
b: {
b1: "b1",
b2: "b2",
},
c: "c",
};
type Join<K extends string, P extends string> = P extends "" ? `${K}` : `${K}.${P}`;
type Paths<T> = T extends object ? Join<keyof T & string, Paths<T[keyof T]>> : ""
type TranslationPath = Paths<typeof enTranslation>;
However, it doesn't work as I intended it to because the 2 keyof T
's in the Paths<T>
type are different. Because of that, the following instance of the type is possible (which would be an invalid path in this case, as there is no b1
property in the a
property of the object enTranslation
):
const test: TranslationPath = "a.b1";
Therefore, my questions are:
keyof T
in both places as one, is there a "type variable" mechanism that allows that?Paths<T>
type?I think you don't need the intermediate paths, since they refer to objects, not to strings, so:
export type Paths<T> = T extends object ? { [K in keyof T]:
`${Exclude<K, symbol>}${Paths<T[K]> extends never ? "" : `.${Paths<T[K]>}`}`
}[keyof T] : never