Runtime And Frontend
The i18n runtime is split by execution context.
| Context | Runtime Shape | Primary Entry Point |
|---|---|---|
| Client | Singleton i18next instance attached to React. | @wavemap/i18n/core/client |
| Server utility | Isolated i18next instance per request or render operation. | @wavemap/i18n/core/server |
| Backend error | Server-only resolver over server--errors JSON resources. | @wavemap/i18n/server |
| Email copy | Server-only adapters over dedicated email--* JSON resources. | @wavemap/i18n/email |
This split prevents request locale bleed on the server and avoids pulling server-only code into client bundles.
Export Boundaries
Section titled “Export Boundaries”Use explicit subpath exports:
import { I18nProvider, useAppTranslation } from "@wavemap/i18n/core/client"import { getI18nClientEnv } from "@wavemap/i18n/env/client"import { getServerT } from "@wavemap/i18n/core/server"import { getI18nServerEnv } from "@wavemap/i18n/env/server"import { resolveServerErrorMessage } from "@wavemap/i18n/server"import { resolvePasswordResetEmailCopy } from "@wavemap/i18n/email"getI18nServerEnv() has a hard browser guard. New helpers should keep this boundary clear: client exports should not
import server-only dependencies, and server exports should not rely on browser globals. Maintaining this curated separation of i18n resources will avoid errors where the front end tries to import code which is only able to run on the server runtime.
Resource Loading
Section titled “Resource Loading”Runtime resource requests use this shape:
/i18n/{version}/{locale}/{namespace}.jsoncreateI18nResourceLoader() decides whether that path is loaded from same-origin assets or from a configured CDN base
URL. Current client and server initialization enable same-origin fallback if CDN loading fails.
In practice:
- Development prefers same-origin API-backed resources.
- Production-like runtimes can use same-origin static assets or a CDN.
- If a CDN base URL is configured and fallback is enabled, a CDN failure retries the same path on the deployed frontend.
Client Runtime
Section titled “Client Runtime”I18nProvider creates the client singleton, initializes shared i18n configuration, preloads the initial namespaces, and
changes language when the route locale changes.
The front end currently passes INITIAL_LOAD_NAMESPACES from the merged package layer. Components then use
useAppTranslation() with the namespaces they need.
const { t } = useAppTranslation(["page--registration", "common--form-validation"])
const tPage = (key: string, values?: Record<string, unknown>) => t(key, { ...values, ns: "page--registration" })
const tValidation = (key: string, values?: Record<string, unknown>) => t(key, { ...values, ns: "common--form-validation" })Both prefixed keys and namespace options appear in the codebase:
t("page--login:form.email.label")t("form.email.label", { ns: "page--login" })Namespace helper wrappers are useful when a component combines page copy with reusable validation or UI copy.
Server Runtime
Section titled “Server Runtime”getServerT(locale, namespaces) creates an isolated i18n instance through createIsolatedI18nInstance().
That shape is intended for:
- Server handlers that need localized user-facing messages.
- SSR utilities that need request-scoped locale state.
- Automated email composition.
Server-side localization should keep namespace choices explicit and avoid using page namespaces for backend-only copy when a dedicated email or server namespace would be clearer.
Backend Error Runtime
Section titled “Backend Error Runtime”Backend error localization uses @wavemap/i18n/server, not the client singleton. Error classes may carry messageKey
and messageInterpolationValues alongside the existing fallback message. The backend error middleware builds the error
envelope, reads the request Accept-Language header, resolves the localized message from server--errors, and writes the
localized display message back into error.message.
The envelope still keeps messageKey and messageInterpolationValues so clients can distinguish stable error identity
from display copy. Locale resolution prefers the best supported Accept-Language entry, supports base-locale matching
such as fr-CA to fr, falls back to the default locale, and finally uses the original route message if the translation
key is missing.
Email Runtime
Section titled “Email Runtime”Automated emails use @wavemap/i18n/email copy adapters. Those adapters import the dedicated email resources, resolve a
declared locale, format ICU values, and return display-ready copy objects for the backend to pass into React Email
templates.
Locale source depends on the email:
- Password reset and resend verification use the recipient user’s saved language when available.
- Admin invitation uses the authenticated sender’s saved language because the invitee does not have Wavemap settings yet.
- Registration-time emails fall back to the default locale when no durable user locale exists.
For future non-user emails, choose the durable locale source before adding the namespace or adapter. Prefer recipient settings when they exist, sender settings when the sender is the only known Wavemap user, request/route locale only when that signal is intentionally part of the flow, and the default locale when no durable user preference exists.
React Email templates should remain renderers. They receive resolved copy and should not load translation resources or choose fallback locales.
Frontend Locale Flow
Section titled “Frontend Locale Flow”Wavemap is URL-locale-first:
/{locale}/...The flow is:
apps/wavemap-front-end/src/middleware/localeRedirect.tsredirects unlocalized app routes to the default locale.src/app/[locale]/layout.tsxowns<html lang={locale}>and the locale-aware provider stack.LocaleContextProviderestablishes active/default/available locale state.I18nProviderinitializes translation resources.- Components call
useAppTranslation()with namespace-scoped keys.
Static assets, API routes, and Next internals are skipped by the locale redirect middleware.
Locale Switching
Section titled “Locale Switching”LocaleSwitcher reads labels from common--locales, writes the wm-locale cookie, replaces the first URL segment, and
preserves query parameters while navigating.
The route remains the source of truth for the active locale. The cookie is a preference used when a request needs a remembered locale.