Assets And Delivery
Source translations live in packages/i18n/locales/**. Production-like runtimes do not read those source files
directly. They consume generated assets under the frontend public directory.
apps/wavemap-front-end/public/i18n/{I18N_VERSION}/...The generated snapshot includes per-locale namespace JSON files and a manifest.json.
Development Flow
Section titled “Development Flow”The runtime loader always asks for resources through the production-shaped URL path:
/i18n/{version}/{locale}/{namespace}.jsonThat is true even in next dev. The development behavior comes from the frontend rewrite layer, not from a separate
development-only resource path inside @wavemap/i18n.
During local development, next.config.js rewrites i18n asset requests to API routes before the request reaches static
asset handling:
/i18n/:version/manifest.json -> /api/i18n/:version/manifest/i18n/:version/:locale/:namespace*.json -> /api/i18n/:version/:locale/:namespace*Those API routes are development-only routes. They reject non-development requests, compare the requested version against
I18N_VERSION when it is present, and read from packages/i18n/locales on disk. Namespace requests return the matching
source JSON file with Cache-Control: no-store; manifest requests discover locale directories, namespace filenames, the
requested version, and the configured default locale.
This gives local development two useful properties:
- The browser and React/i18next runtime exercise the same public URL shape that built runtimes use.
- Ordinary translation JSON edits are available on refresh without regenerating
apps/wavemap-front-end/public/i18n/**.
The development manifest validator compares expected locales and namespaces against discovered source resources so namespace or locale drift is visible early.
The loader selection still runs in development. I18nProvider reads NEXT_PUBLIC_I18N_VERSION and
NEXT_PUBLIC_I18N_CDN_BASE_URL, then calls initI18nCommon(). Server-side isolated i18n helpers read I18N_VERSION and
I18N_CDN_BASE_URL through getI18nServerEnv(). Both paths pass those values to createI18nResourceLoader() through
i18next-resources-to-backend, but they also set preferSameOrigin when NODE_ENV=development.
preferSameOrigin is the important local switch. Even if a CDN base URL is present in the environment, the loader uses
the app-relative /i18n/{version}/... path first. In next dev, that app-relative path lands on the source-backed API
routes above, so local work does not accidentally bypass the rewrites and fetch stale generated or remote assets.
Production-Like Flow
Section titled “Production-Like Flow”Production-like builds keep the same runtime URL shape:
/i18n/{version}/{locale}/{namespace}.jsonThe difference is that the development rewrites are not installed. The same-origin path now resolves to generated static files in the frontend image:
apps/wavemap-front-end/public/i18n/{I18N_VERSION}/{locale}/{namespace}.jsonapps/wavemap-front-end/public/i18n/{I18N_VERSION}/manifest.jsonbuild:i18n-assets creates that versioned snapshot from packages/i18n/locales: it validates each source JSON file,
copies each locale namespace into the matching public asset folder, and writes a manifest containing the version,
generation timestamp, locale list, namespace list, and default locale.
The runtime loader still selects the delivery source. createI18nResourceLoader() receives:
version, fromNEXT_PUBLIC_I18N_VERSIONon the client andI18N_VERSIONin server-side isolated i18n helpers.assetBaseURL, fromNEXT_PUBLIC_I18N_CDN_BASE_URLon the client andI18N_CDN_BASE_URLon the server.preferSameOrigin, which is false outside development.fallbackToSameOriginOnError, which current client and isolated server initialization leave enabled.
That produces these production-like branches:
- No CDN base URL: load same-origin assets from the deployed frontend.
- CDN base URL configured: strip trailing slashes from the CDN base URL and try
{cdnBase}/i18n/{version}/{locale}/{namespace}.jsonfirst. - CDN failure with fallback enabled: retry
/i18n/{version}/{locale}/{namespace}.jsonon the deployed frontend. - CDN failure with fallback disabled: fail immediately with the CDN fetch error.
Every resource fetch uses cache: "no-store" at the loader level. CDN and platform caching can still sit in front of the
asset files, but the app-side fetch call does not request browser or server fetch-cache reuse.
Same-origin fallback only works when the deployed frontend image includes the matching generated asset version. If the
CDN contains I18N_VERSION=x but the frontend image only includes public/i18n/y, the fallback path cannot repair a CDN
miss for version x. That is why build, deploy, and CDN publish workflows must keep I18N_VERSION,
NEXT_PUBLIC_I18N_VERSION, generated assets, and any CDN upload target aligned.
The production-like path is therefore not a separate i18n runtime. It is the same i18next backend loader pointed at a different delivery layer: generated same-origin files by default, optional CDN first, and same-origin generated files as the fallback lane.
Asset Scripts
Section titled “Asset Scripts”The i18n package owns asset generation and validation:
pnpm -C packages/i18n build:i18n-assetspnpm -C packages/i18n validate:i18n-assetspnpm -C packages/i18n build-and-validate:i18n-assetsValidation checks that:
- The generated root exists.
- The manifest exists.
- The manifest version matches the package/runtime i18n version.
- Manifest locales and namespaces match the source translation surface.
- Every generated locale contains the expected namespace set.
The root CI front-end smoke lane generates and validates the static asset snapshot before building the frontend runtime. That keeps browser smoke focused on whether the built app can boot and behave with the exact assets the image contains.
CDN Tooling
Section titled “CDN Tooling”CDN publishing is modeled as an optional delivery layer over the same generated snapshot.
Package entry points:
pnpm -C packages/i18n publish:i18n-assets:cdnpnpm -C packages/i18n prune:i18n-assets:cdnThe current provider adapter model is intentionally narrow:
- Azure provider support exists.
- AWS provider support is stubbed and errors as unsupported for now.
- Publish uploads generated files for one version.
- Prune keeps the configured number of recent versions plus the current version.
AWS S3 / CloudFront locale hosting should update the same provider-neutral delivery contract rather than changing the frontend environment vocabulary.
Deployed-Dev Contract
Section titled “Deployed-Dev Contract”The first deployed-dev path uses same-origin generated frontend assets. Pulumi outputs expose an i18nAssets contract so
future CDN delivery can fit into the same shape:
i18nAssets.deliveryModei18nAssets.publicBaseURLi18nAssets.version
Deploy validation derives frontend build inputs from Pulumi-shaped outputs and repo-owned i18n sources. If i18n changes alter asset generation, public route shape, locale defaults, or CDN semantics, update deployed-dev contract checks in the same work pass.