Skip to content

Instantly share code, notes, and snippets.

@hydrogen602
Created January 25, 2025 21:57
Show Gist options
  • Save hydrogen602/aee983f39c86816ec9b338dafa056635 to your computer and use it in GitHub Desktop.
Save hydrogen602/aee983f39c86816ec9b338dafa056635 to your computer and use it in GitHub Desktop.
// This snippet's goal is to bring type-safety to i18n lookups,
// e.g. ensure code like the following uses the right key.
// const { t } = useTranslation();
// return <p>{ t['invoice_page.title'] }</p>
// Split a string on periods
type DotSplit<S extends string> =
S extends `${infer Head}.${infer Tail}`
? [ Head, ...DotSplit<Tail> ]
:( S extends string ?
[ S ]
: never);
// Lets assume we have a type that describes our i18n translations
interface I18nTranslations {
'title': string,
'welcome': string,
'invoice_page': {
'title': string,
'invoice_number': string,
},
'settings_page': {
'name': string,
'phone': string,
}
}
// Now we can verify any given string is a valid path to a string in our translation dict with the following
type DotSplit2<S extends string, T> =
S extends `${infer Head}.${infer Tail}`
? (
Head extends keyof T & string ?
DotSplit2<Tail, T[Head]>
: never
)
:( S extends keyof T & string ?
T[S]
: never);
type TypeMatch<A, B, Out> = A extends B ? B extends A ? Out : never : never;
type Path<Key extends string, T> = TypeMatch<DotSplit2<Key, T>, string, Key>;
const valid_string = <T extends string>(path: Path<T, I18nTranslations>): string => path;
const x1 = valid_string('invoice_page.title');
// These cause type-errors
//const x2 = valid_string('invoice_page.phone');
//const x3 = valid_string('invoice_page');
// React hook to make use of this type-safe usage of translations
export function useT() {
const { t } = useTranslation();
return useCallback(<T extends string>(path: Path<T, I18nTranslations>): string => {
return t(path);
}, [t]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment