Skip to content

Instantly share code, notes, and snippets.

@ydrea
Created August 21, 2025 14:11
Show Gist options
  • Save ydrea/321f857648da3e45024ba15e3e9f69d0 to your computer and use it in GitHub Desktop.
Save ydrea/321f857648da3e45024ba15e3e9f69d0 to your computer and use it in GitHub Desktop.
i18n UI translations
// translations+interpolations.ts
function defineTranslations<
T extends Record<string, Record<string, string>>
>(t: T & Record<keyof T, Record<keyof T[keyof T], string>>) {
return t;
}
export const translations = defineTranslations({
en: {
hello: "Hello",
goodbye: "Goodbye",
welcome: "Welcome, {name}!",
},
hr: {
hello: "Bok",
goodbye: "Doviđenja",
welcome: "Dobrodošao, {name}!",
},
});
export type Language = keyof typeof translations;
export type TranslationKey = keyof typeof translations[Language];
// Extract placeholders from a string like "Welcome, {name}!"
type ExtractPlaceholders<S extends string> =
S extends `${string}{${infer Param}}${infer Rest}`
? Param | ExtractPlaceholders<Rest>
: never;
// Params object type for a given language+key
type Params<L extends Language, K extends TranslationKey> =
ExtractPlaceholders<typeof translations[L][K]> extends never
? {}
: Record<ExtractPlaceholders<typeof translations[L][K]>, string>;
// Our `t()` function with interpolation
export function t<L extends Language, K extends TranslationKey>(
lang: L,
key: K,
params?: Params<L, K>
): string {
let text = translations[lang][key];
if (params) {
for (const [param, value] of Object.entries(params)) {
text = text.replace(new RegExp(`{${param}}`, "g"), value?.toString() ?? "");
}
}
return text;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment