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.
INITIAL_LOAD_NAMESPACES stays intentionally narrow. When a component requests an ordinary page or domain namespace
that has not loaded yet, useAppTranslation() suspends that consumer on the namespace request. The package-owned
I18nProvider supplies the matching Suspense boundary and loading fallback, so components do not call t() against a
pending namespace or emit strict missing-key warnings during the loading window.
import { tFunctionCopy } from "@wavemap/i18n"
const { t } = useAppTranslation(["page--registration", "common--form-validation"])
const tPage = tFunctionCopy(t, "page--registration")
const tValidation = tFunctionCopy(t, "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" })Use the package-owned tFunctionCopy(t, namespace) when a component combines page copy with reusable validation or UI copy. It preserves the supplied translator’s locale binding and forwards interpolation values without mutating them. The helper is available from @wavemap/i18n and @wavemap/i18n/core in both server and client code.
Label builders and component callbacks use TScopedTranslation from the same package instead of defining a local translation signature or importing one from another feature. Keep the richer native i18next TFunction at runtime boundaries such as getServerT() and useAppTranslation(); the shared callback describes string-valued copy after namespace scoping. Translation semantics belong to @wavemap/i18n, which already owns the runtime and its types.
Pseudolocalization Runtime
Section titled “Pseudolocalization Runtime”Pseudolocalization is a diagnostic runtime mode, not a separate locale. When enabled, @wavemap/i18n registers an
i18next post-processor named wavemapPseudolocalization. Translated string results are transformed after normal lookup,
interpolation, and ICU formatting.
The default character set is accented, which keeps text readable while making translated copy visually obvious:
Log In -> [Ļōĝ Īń]Additional built-in character sets are available for stress and entertainment:
hieroglyphicswingdings
The package also exposes a custom replacement-map interface for local experiments and future test cases:
import { pseudoLocalize } from "@wavemap/i18n/core"
pseudoLocalize("Login", { replacementMap: { L: "1", o: "0", g: "9", i: "!", n: "^", },})Runtime switches:
| Environment Value | Scope | Meaning |
|---|---|---|
PSEUDOLOCALIZATION_ACTIVE=true | Server/runtime helper | Enables server-side isolated i18next processing. |
NEXT_PUBLIC_PSEUDOLOCALIZATION_ACTIVE=true | Browser bundle | Enables client-side i18next processing. |
PSEUDOLOCALIZATION_CHARACTER_SET=accented | Server/runtime helper | Selects the server-side replacement set. |
NEXT_PUBLIC_PSEUDOLOCALIZATION_CHARACTER_SET | Browser bundle | Selects the client-side replacement set. |
Pseudolocalization is most useful when the app is otherwise running normally. A translated string should show pseudolocalized characters. A naked JSX string remains ordinary text and can be detected by a browser sweep.
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.