Contributor Workflows
Treat i18n changes as cross-cutting changes. A small locale or namespace edit can touch runtime config, generated assets, tests, CI, and domain-code display contracts.
Before Starting
Section titled “Before Starting”Identify which surface owns the change:
- Ordinary UI copy: update locale sheets under
packages/i18n/locales. - New reusable namespace: add constants in the correct
commonorwavemaplayer and include them throughmerge. - New code-driven display value: update source constants, translations, and the domain contract manifest.
- New backend error copy: use
server--errorsand preserve route fallbackmessagetext. - New automated email copy: prefer a dedicated
email--*namespace over reusing a page namespace.
Add A Locale
Section titled “Add A Locale”- Add the locale code to the declared locale constants and any required environment allow-list.
- Add a full namespace JSON set under
packages/i18n/locales/<new-locale>/. - Mirror English (
en) key paths, value shapes, and root_metashape. - Preserve the same ICU interpolation, plural, and select contracts as English.
- Keep ordinary key segments lower-kebab.
- Run
pnpm format:i18n. - Run
pnpm verify:i18n. - Smoke the locale redirect, locale switcher, and representative UI flows with interpolation and plurals.
Add A Namespace
Section titled “Add A Namespace”- Add a namespace constant in the owning layer:
commonfor reusable cross-project concepts.wavemapfor product-specific copy.
- Include the namespace in the merged namespace set if it should be runtime-visible.
- Confirm the namespace string exactly matches the locale sheet filename stem.
- Add
<namespace>.jsonfor every supported locale. - Start each sheet with
_meta. - Follow the canonical sheet ordering rules.
- Add the namespace to
INITIAL_LOAD_NAMESPACESonly when it is needed at startup. - Run
pnpm format:i18n. - Run
pnpm verify:i18n.
Add A Translated Domain Code
Section titled “Add A Translated Domain Code”Use this flow when a source code value needs a localized label.
- Define or update the canonical source constant.
- Add the value to the exported
AVAILABLE_*array. - Ensure backend lookup seeds map over the canonical array instead of hardcoding row values.
- Add or update the seed-source contract entry when the code set has a backend base seed.
- Choose a predictable translation key prefix, such as
statuses.<code>ortypes.<code>. - Add or update the domain translation contract entry.
- Add the required keys to every locale.
- Run
pnpm verify:i18n. - Add runtime mapping tests where UI code derives translation keys dynamically.
Add Or Migrate A Server Error Message
Section titled “Add Or Migrate A Server Error Message”Use this flow when an API error response should include localized display copy.
- Confirm the message is user-facing backend copy, not a log line, operator diagnostic, or raw exception detail.
- Choose a
server--errorskey with a domain-first root, such asauth.*,artist.*, orquery.*. - Keep the existing fallback
messageuseful and addmessageKeyplusmessageInterpolationValuesto the thrown HTTP error. - Keep
messageInterpolationValuesentries primitive: string, number, boolean, ornull. - Add the key to every locale’s
server--errors.json. - Use ICU for dynamic values and update
_meta.interpolationfor every affected locale. - Add or update route, middleware, or resolver tests when the key covers meaningful behavior.
- Run
pnpm format:i18n. - Run
pnpm verify:i18n.
Backend error middleware resolves the localized display message from Accept-Language. Clients should treat
messageKey as stable identity and message as display fallback or already-localized copy.
Add Or Migrate Automated Email Copy
Section titled “Add Or Migrate Automated Email Copy”Use this flow when backend email composition needs localized subject, preview, body, action, or fallback-link text.
- Choose an existing dedicated
email--*namespace, or add a new one for the email family. - Keep related subject, preview, headings, body copy, action labels, and fallback-link text together.
- Add a backend copy adapter that resolves the locale, formats ICU values, and returns display-ready copy.
- Pass resolved copy into the React Email template; keep the template independent from translation loading.
- Choose a durable locale source:
- Recipient saved language when the recipient user already exists.
- Authenticated sender saved language for invitations to people without Wavemap settings.
- Registration-time locale when creating a user before saved settings are available.
- Default locale when no user preference exists yet.
- Add or update adapter tests for supported locales, ICU values, and default-locale fallback.
- Run
pnpm format:i18n. - Run
pnpm verify:i18n.
Localize A Large Reusable Component
Section titled “Localize A Large Reusable Component”Use this flow when a reusable component owns internal visible labels, ARIA labels, status text, or dynamic formatter copy across several subcomponents.
- Keep the component independent from Wavemap translation keys and namespaces.
- Translate at the call site or in a small page/domain adapter, then pass resolved strings and formatter functions.
- Use a
common--*namespace for product-neutral component chrome. - Use the page or domain namespace for workflow-specific copy, fallback errors, and context-sensitive labels.
- Add a grouped
labelsprop whose groups mirror the component’s internal label supply chain. - Include formatter functions for dynamic strings, such as page numbers, item counts, toast bodies, or grouped overflow labels.
- Resolve defaults and any legacy scalar label props in one adapter near the top-level component.
- Pass complete label groups down to internal subcomponents instead of making each call site know the subcomponent tree.
- Add tests for defaults, legacy scalar props if they remain supported, and one fully customized label map.
Prefer small helper adapters when a component needs a large or nested label map. The adapter should sit near the page or
domain wrapper that owns the translation context, resolve t() once, and return the complete display-ready label shape
expected by the reusable component. This keeps namespace details out of generic UI code without forcing every call site
to know the component’s full internal subcomponent tree.
Components like <Pagination />, <Carousel />, <FileUploadSurface />, <SortAndFilterPanel />,
<TypeaheadSearch />, <CompactTypeaheadSearch />, <Breadcrumbs />, date/time/numeric inputs, and the table
filtering stack can serve as good examples of this approach.
Example label-map shapes:
| Surface | Pattern |
|---|---|
Pagination | Resolve one top-level labels map, then slice it into primary controls, page counter, page input, and items-per-page labels. |
Carousel | Pass formatter functions for live regions, slide labels, dot labels, previous/next controls, and close controls. |
MediaCarousel | Layer media dialog copy and media-specific defaults on top of the generic carousel label contract. |
FileUploadSurface | Resolve one label map for surface controls, preview items, sheet chrome, gallery/table views, row menus, alt-text popovers, and toast copy. |
| Table filtering | Let Table resolve table-filter labels once, then pass popover, clause-row, and dynamic argument-input labels through the stack. |
SortAndFilterPanel | Resolve sidebar labels once, then pass header/footer, active sort, active filter group, and nested filter-clause labels downward. |
| Typeahead/combo controls | Keep English defaults for reuse while page/domain wrappers supply grouped labels for artist/event search and filter argument inputs. |
Breadcrumbs | Let the component own fallback navigation and overflow chrome labels while breadcrumb item names remain caller-provided content. |
| Date/time/numeric inputs | Keep field names caller-owned, while internal control groups, picker triggers, and picker calendar labels come from small label maps. |
Reusable components can keep English defaults for non-Wavemap reuse, but product surfaces should supply localized label
maps. The component should receive display-ready text, not t, namespace names, or Wavemap translation keys.
Update A Translation Sheet
Section titled “Update A Translation Sheet”When changing existing copy:
- Update all affected locales, or intentionally leave the non-English copy as a coordinated follow-up.
- Update
_meta.lastModified. - Update
_meta.interpolationif ICU tokens changed. - Run
pnpm format:i18n. - Run
pnpm verify:i18n.
Review Checklist
Section titled “Review Checklist”Before merging i18n-related changes, check:
- Namespace constants match locale sheet filename stems.
- Every locale has the same namespace set.
- Key paths and value shapes match English.
- ICU runtime variables and branch keys match English.
_meta.interpolationmatches parsed message tokens.- Translation sheet ordering is canonical.
- Dynamic domain-code values have translation keys in every locale.
- Backend base seed files still map over canonical source arrays.
- Backend error
messageKeyvalues have matchingserver--errorsentries when the error is user-facing. - Email templates receive resolved copy instead of importing translation resources directly.
- Reusable components receive grouped label maps when they own internal visible copy.
- Generated static assets still validate if the change affects deployment or smoke runtime behavior.