Consumption
How the app, the player, the website and the admin load these messages.
Apps use the package in one of two ways:
- Over HTTP, through i18next (
apps/app,apps/player). The JSON files are served aslocales/<lang>/<ns>.jsonand loaded at runtime byi18next-http-backend. The package is only imported for its types (and key registries). - As a direct import (
apps/web,packages/training,apps/app,apps/admin). The*Messagesobjects or the rotation entries are imported like any other module.
Vite apps (app and player)
Both Vite apps declare an I18N_LOCALES object in vite.config.ts that lists the namespaces they serve, each pointing at a folder of this package:
// apps/player/vite.config.ts
const I18N_LOCALES = {
languages: [...SUPPORTED_LANGUAGES],
packages: [
{
localesDir: "../../packages/i18n-messages/src/locales/common",
namespace: "common",
},
// bridge, game, player, billing …
],
};I18N_LOCALES is used in three places:
| Phase | Mechanism |
|---|---|
| Dev | An i18n-dev-server middleware serves /locales/<lang>/<ns>.json straight from packages/i18n-messages/src/locales, and i18nextHMRPlugin hot-reloads edits |
| Build | createI18nCopyTargets(I18N_LOCALES) (from @workspace/i18n/vite) copies each file to dist/locales/<lang>/<ns>.json |
| Build | createI18nLocaleHashes(...) hashes each file's content; createI18nLoadPath appends ?v=<hash> to its URL, so a changed file gets a new URL |
The namespace names in I18N_LOCALES, in the i18next ns option and in the i18next.d.ts resources must match.
apps/app
- Languages:
frandenonly (DEFAULT_LANGUAGEisfr). - Namespaces:
common,errors,teaching,students,deal-collections,public-sessions,calendar,deals,deal-editor,metadata,game,account,billing,sharing-group,bridge,tour,usage,dev-tools,primitives,rotation,rich-editor. fallbackLng: DEFAULT_LANGUAGE: a missing key falls back to French.- Typed keys come from
@workspace/i18n-messages/types(see Namespaces).
apps/player
- Languages: all 11 (
apps/player/src/lib/i18n/constants.ts). - Namespaces:
player(default),bridge,game,common,billing. fallbackLng: false. A key missing in the target language renders as the raw key, not as the English text. The runtime never hides gaps; the translation generator is what fills them.- Unsupported detected languages are filtered out by
supportedLngsand fall back toSUPPORTED_LANGUAGES[0](en). - Translations are cached in localStorage (
i18next-localstorage-cache). Bump the per-languagecache.versionsinapps/player/src/lib/i18n/instance.tswhen a namespace gains or renames keys. - At build time,
apps/player/vite/unsupported-page.tsreadsplayer/<lang>.jsonfrom disk to inlineunsupported.*intounsupported.html. That is why the key audit has an allowlist entry for it.
apps/web
The Next.js site does not use i18next. It imports the web namespace as an object and reads it by property:
// apps/web/src/lib/get-dictionary.ts
import { webMessages } from "@workspace/i18n-messages/web"
export type Dictionary = (typeof webMessages)["en"]
export function getDictionary(lang: LanguageCode | (string & {})): Dictionary {
const locale = parseLocale(lang)
return webMessages[locale]
}It also reads bridgeMessages[locale].labels.bids directly for its bid labels (apps/web/src/lib/bridge-strings.ts).
Rotation (domain and admin)
packages/training/src/domain/shared/rotater.ts, apps/app/src/lib/rotater.ts and apps/admin/src/lib/rotater.ts build their rotaters with buildAllRotationEntries() from @workspace/i18n-messages/rotation/entries. @workspace/bridge stays free of translation data: the caller supplies the language table.