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 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.title
  • hero.role
  • contact.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.