Skip to content

Guardrails

i18n failures are often related to string-identity problems rather than TypeScript failures. A code value, database seed row, translation key, namespace filename, or generated asset path can drift while the compiler still stays green.

Wavemap’s current posture is to make those identities explicit and continuously checked.

Terminal window
pnpm format:i18n
pnpm verify:i18n

format:i18n rewrites source translation sheets into canonical order.

verify:i18n checks the source translation surface and fails CI when a contract drifts.

The source verifier currently checks:

  • Translation JSON parses correctly.
  • Registered namespace constants match locale filenames.
  • Every locale has the expected namespace files.
  • Non-English locales mirror English (en) key shape.
  • Ordinary translation key segments use the expected lower-kebab style.
  • _meta shape, interpolation hints, and last-modified conventions are valid.
  • Translation sheets are canonically ordered.
  • ICU syntax parses through intl-messageformat.
  • Non-English locales preserve English interpolation, plural, and select contracts.
  • Domain-code translation coverage matches source contract manifests.
  • Backend base-seed files import and map over expected canonical arrays.

This is intentionally source-level. It catches most drift before tests need a running app or database.

server--errors and email--* namespaces are checked through the same source-level rules as the rest of the translation surface: locale parity, key shape, ICU validity, interpolation metadata, and canonical ordering.

Namespace Registry And Sheet Filename Parity

Section titled “Namespace Registry And Sheet Filename Parity”

verify:i18n enforces a two-way match between registered namespace names and source translation sheet filenames.

The verifier builds the registered namespace set from AVAILABLE_NAMESPACES, following the layered namespace arrays in common, wavemap, email, and server. It builds the source namespace set from packages/i18n/locales/<locale>/*.json, using each filename without .json as the namespace name.

This catches three drift cases:

  • A namespace constant is registered but one or more locale sheets are missing.
  • A translation sheet exists on disk but is not included in the runtime namespace registry.
  • One locale has a namespace sheet that another locale does not.

The filename stem is therefore part of the runtime contract. For example, a namespace value of common--pagination must be backed by common--pagination.json in every supported locale.

.github/workflows/ci.yml has a dedicated i18n_verification job after verify_tooling_and_code.

That job runs:

Terminal window
pnpm verify:i18n

Keeping it separate makes i18n failures visible as i18n failures instead of discovering them indirectly through Playwright smoke tests or missing labels in the browser.

The front-end E2E smoke lane also runs:

Terminal window
pnpm -C packages/i18n build-and-validate:i18n-assets

That proves the generated static asset snapshot before the frontend is built and started.

Some display values are code-driven. Examples include event statuses, event types, venue statuses, artist link destinations, user-facing roles, display modes, themes, and external institutions.

The manifest at packages/i18n/scripts/i18n/helpers/domainContracts.js ties an exported source array to:

  • A target translation namespace.
  • A key prefix.
  • The set of required localized keys.

At a high level, the verifier:

  1. Reads each manifest entry.
  2. Parses the TypeScript source file with the TypeScript compiler API.
  3. Resolves exported string arrays such as AVAILABLE_EVENT_STATUS_CODES.
  4. Builds required keys such as statuses.upcoming.
  5. Verifies every supported locale has those keys.
  6. Fails if a checked translation sheet contains stale keys for removed or unknown codes.
  7. Requires checked code values to be lower-kebab so they can safely become key segments.

Translation files should render localized labels. They should not define the allowed domain states.

The same verifier checks backend base-seed source wiring through DOMAIN_SEED_SOURCE_CONTRACTS.

Each seed contract ties a backend base seed file to the canonical exported array it should use for lookup-table rows. The check verifies that the seed imports the expected array and maps over it when building seed rows.

Current coverage includes roles, permissions, artist/event/event-series/venue lookup seeds, and media types.

Database-backed row parity is still deferred until the CI database path is stable enough.

The verifier checks the source translation sheets, but it does not yet scan backend source for every messageKey usage. New user-facing backend error migrations should therefore add targeted route, middleware, or resolver tests when a missing or wrong key would be user-visible.

Email copy adapters are the testable boundary for automated email localization. Tests should prove the adapter resolves supported locales, falls back to the default locale, and formats ICU values before the React Email template receives copy.

Reusable component label maps follow a similar split: namespace files are verified by verify:i18n, while component tests prove the label map shape, default labels, legacy scalar props, and formatter behavior.

Known deferred i18n guardrails:

  • Auth permission display coverage until a user-facing admin/permissions UI exists.
  • Database-backed checks proving seeded lookup-table rows match canonical source arrays exactly.
  • Static analysis that matches backend messageKey usage to server--errors entries.
  • A manifest or structural verifier for large reusable component label-map contracts.
  • Targeted tests for CDN publish/prune behavior before enabling AWS i18n CDN publishing.
  • A decision on whether generated i18n asset validation should become its own named CI job.

The internal dev-debug role is intentionally excluded from checked role.* translation coverage.