Recipe

i18n key namespacing & organization

A scalable convention for structuring translation keys across pages, components, and shared UI.

Namespace hierarchy

common:
  actions:
    save: "Save"
    cancel: "Cancel"
    delete: "Delete"
  errors:
    generic: "Something went wrong"
    network: "Network error"

pages:
  dashboard:
    title: "Dashboard"
    welcome: "Welcome back, {name}"
  settings:
    title: "Settings"
    billing:
      title: "Billing"

components:
  modal:
    confirm_title: "Are you sure?"
    confirm_body: "This action cannot be undone."

Rules

  • common — shared across the entire app (buttons, errors, dates)
  • pages — one subtree per route, mirroring the URL structure
  • components — reusable UI blocks, named after the component
  • Never nest deeper than 4 levels
  • Use snake_case for all keys
  • Placeholder syntax: {variable}

Usage example

import { useTranslation } from "react-i18next";

function DashboardGreeting({ name }: { name: string }) {
  const { t } = useTranslation();
  return <h1>{t("pages.dashboard.welcome", { name })}</h1>;
}

Tip: Run a lint rule that flags any key not matching common.* | pages.* | components.* to prevent ad-hoc keys from creeping in.

Meridian — getnimbus.net