Translations based on loose strings have a silent problem: nothing warns you when you mistype a key or when a translation is missing in a language. The error shows up in production, not in your editor.
The goal
That t("metadata.title") autocompletes and that t("metdata.title") is a compile-time type error.
The solution
I built @ariaskit/astro-i18n@ariaskit/astro-i18nType-safe i18n for Astro: nested-key autocompletion, variable interpolation and a visual development inspector. exactly for this. You define your translations in JSON and derive the type:
import base from '@/i18n/_template.json' with { type: 'json' };
export type BaseJSON = typeof base;
Then the generic does all the magic:
const { t, locale } = useI18n<BaseJSON>({ ssg: { astro: Astro } });
From there, these keys are checked by TypeScript:
metadata.titlehero.rolecontact.email
What I learned
The recursive DotNotation type walks the JSON up to 5 levels deep. Beyond that, it’s best to flatten the structure. And a key detail: the library resolves language state from Astro.currentLocale, so never hardcode the locale.
This very blog you’re reading uses exactly this approach across three languages.