Skip to content

i18n Pattern

Translation handling with i18next.

Structure

src/i18n/
├── keys.ts       # Type-safe translation keys
├── english.ts    # English translations
├── czech.ts      # Czech translations
└── language.ts   # i18next configuration

Translation Keys

// keys.ts
export const TranslationKeys = {
  shared: {
    button: {
      save: "shared.button.save",
      cancel: "shared.button.cancel",
      delete: "shared.button.delete",
    },
    aria: {
      close: "shared.aria.close",
      menu: "shared.aria.menu",
    },
  },
  markers: {
    title: "markers.title",
    created: "markers.created",
    deleted: "markers.deleted",
  },
} as const;

Translations

// 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",
  },
};

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

  1. Add key to keys.ts
  2. Add translation to english.ts
  3. Add translation to czech.ts
  4. Use with t(TranslationKeys.x.y)

Aria Labels

Every interactive element needs an aria-label using i18n:

// Add to keys.ts
shared: {
  aria: {
    deleteMarker: "shared.aria.deleteMarker",
  },
}

// Add to english.ts
aria: {
  deleteMarker: "Delete marker",
}

// Use in component
<IconButton aria-label={t(TranslationKeys.shared.aria.deleteMarker)}>
  <DeleteIcon />
</IconButton>
  • Forms — Validation messages