i18n Pattern¶
Translation handling with i18next.
Structure¶
src/i18n/
├── english.ts # English translations — schema source of truth
├── czech.ts # Czech translations — structurally checked against english
├── keys.ts # Derives TranslationKeys from english.ts
└── language.ts # i18next configuration
Translations¶
english.ts is the single schema source of truth:
// english.ts
export const english = {
shared: {
button: {
save: "Save",
cancel: "Cancel",
delete: "Delete",
},
aria: {
close: "Close",
menu: "Open menu",
},
},
markers: {
title: "Markers",
created: "Marker created",
deleted: "Marker deleted",
},
};
czech.ts must mirror the same structure — enforced at compile time:
Translation Keys¶
TranslationKeys is derived from english.ts at runtime — keys.ts recursively walks the object and replaces every leaf with its dot-path. Never hand-edit key paths.
// keys.ts (derivation, not a hand-maintained map)
export const TranslationKeys = buildKeys(english, "") as TranslationKeyTree<typeof english>;
TranslationKeys.shared.button.save; // "shared.button.save"
The shape of TranslationKeys follows typeof english, so a typo in a key path is a compile error. Unit tests in tests/unit/i18n/keys.test.ts verify every derived key resolves to a string in both languages.
Usage¶
const { t } = useTranslation();
// Simple
<Button>{t(TranslationKeys.shared.button.save)}</Button>
// With interpolation
t(TranslationKeys.validation.maxLength, { max: 200 })
// "Maximum length is {{max}} characters" → "Maximum length is 200 characters"
// Aria labels (mandatory for accessibility)
<IconButton aria-label={t(TranslationKeys.shared.aria.close)}>
<CloseIcon />
</IconButton>
Adding New Keys¶
- Add translation to
english.ts - Add translation to
czech.ts(thesatisfiescheck fails until you do) - Use with
t(TranslationKeys.x.y)— the key is available automatically
Aria Labels¶
Every interactive element needs an aria-label using i18n:
// Add to english.ts (and czech.ts)
aria: {
deleteMarker: "Delete marker",
}
// Use in component
<IconButton aria-label={t(TranslationKeys.shared.aria.deleteMarker)}>
<DeleteIcon />
</IconButton>
Related¶
- Forms — Validation messages