--- description: Text localization via messages/ bundles and useMessages() globs: messages/**/*.{ts,json} alwaysApply: false --- # Text localization All user-visible copy lives in the typed messages bundle under `messages/en/` and is read via `useMessages()` (fully typed) or `useTranslation()` (dot notation). Never hard-code user-facing strings in components. ## File layout - `messages/en/.json` for single-file areas (`common.json`, `navigation.json`, `metadata.json`). - `messages/en//.json` for areas with multiple buckets: `components/*.json`, `pages/*.json`, `create/*.json`. One JSON per component / page / create-flow step — don't shoehorn unrelated copy into a shared file. - Optional `"_comment"` at the top of a JSON documents the bundle's purpose. ## Registration — required Every new JSON must be wired into `messages/en/index.ts`: ```typescript import createConflictManagement from "./create/conflictManagement.json"; export default { // … create: { conflictManagement: createConflictManagement, }, }; ``` The default export **is** the type source for `useMessages()`; skipping this step means consumers can't read your strings and TypeScript won't flag the gap. ## Access pattern ```typescript import { useMessages } from "../contexts/MessagesContext"; const m = useMessages(); const title = m.create.conflictManagement.page.compactTitle; // fully typed ``` Use `useTranslation(namespace)` only when you need dot-path lookup by dynamic key; prefer direct property access for the type safety. ## Key conventions - **Structural keys**: camelCase (`compactTitle`, `sectionHeadings.corePrinciple`). - **Content ids**: match the id consumers already use (card id, step id, URL segment) — typically kebab-case (`"in-person-meetings"`, `"peer-mediation"`).