Bridge Training
i18n Messages

Namespaces

Subpath exports, message objects, types and key registries.

Each namespace is a folder under src/locales/. Its index.ts exports a <camelCase>Messages object keyed by language code, and each value is the parsed JSON for that language.

import { dealsMessages } from '@workspace/i18n-messages/deals';

dealsMessages.en; // content of src/locales/deals/en.json
dealsMessages.fr; // content of src/locales/deals/fr.json

Namespace list

NamespaceSubpath exportMessages exportLanguages
account./accountaccountMessagesen, fr
billing./billingbillingMessagesall 11
bridge./bridgebridgeMessagesall 11
calendar./calendarcalendarMessagesen, fr
common./commoncommonMessagesall 11
deal-collectionsnone (root only)dealCollectionsMessagesen, fr
deal-editor./deal-editordealEditorMessagesen, fr
deals./dealsdealsMessagesen, fr
dev-tools./dev-toolsdevToolsMessagesen, fr
errors./errorserrorsMessagesen, fr
game./gamegameMessagesall 11
metadata./metadatametadataMessagesen, fr
player./playerplayerMessagesall 11
primitives./primitivesprimitivesMessagesall 11
public-sessions./public-sessionspublicSessionsMessagesen, fr
rich-editor./rich-editorrichEditorMessagesen, fr
rotation./rotationrotationMessagesall 11
sharing-group./sharing-groupsharingGroupMessagesen, fr
students./studentsstudentsMessagesen, fr
teaching./teachingteachingMessagesen, fr
tournonetourMessages (not re-exported from the root)en, fr
usagenone (root only)usageMessagesen, fr
web./webwebMessagesall 11

The root export @workspace/i18n-messages re-exports every *Messages object except tourMessages, plus primitivesResources, PrimitivesTranslations, SUPPORTED_LANGUAGES and SupportedLanguage. Apps that only need one namespace import from its subpath.

Other subpath exports

SubpathContent
./typesOne XxxNamespace type per namespace (the shape of its en.json)
./supported-languagesSUPPORTED_LANGUAGES and SupportedLanguage (11 codes)
./primitives-resourcesprimitivesResources and PrimitivesTranslations
./bridge/keysStatic key registries for the bridge namespace
./rotation/entriesbuildRotationEntry and buildAllRotationEntries for the @workspace/bridge rotater

Types

@workspace/i18n-messages/types derives each namespace type from its English file:

export type DealsNamespace = (typeof dealsMessages)["en"];
export type PlayerNamespace = (typeof playerMessages)["en"];

Apps use these types to give i18next typed keys. From apps/app/src/lib/i18n/i18next.d.ts:

import type {
  CommonNamespace,
  DealsNamespace,
  // …
} from "@workspace/i18n-messages/types";

declare module "i18next" {
  interface CustomTypeOptions {
    defaultNS: typeof defaultNS;
    resources: {
      common: CommonNamespace;
      deals: DealsNamespace;
      // …
    };
  }
}

primitivesResources

primitivesResources groups the primitives and rotation namespaces for each of the 11 languages, ready to pass to i18next resources:

import {
  primitivesResources,
  type PrimitivesTranslations,
} from '@workspace/i18n-messages/primitives-resources';

primitivesResources.en; // { primitives: primitivesMessages.en, rotation: rotationMessages.en }
primitivesResources.el; // { primitives: primitivesMessages.el, rotation: rotationMessages.el }

// PrimitivesTranslations = (typeof primitivesMessages)["en"]

Bridge key registries

./bridge/keys maps @workspace/bridge values to keys in the bridge namespace. Callers look up a key with a typed value instead of building it in a template literal, so every key stays a string literal that you can grep for and that the key audit can see. Each registry uses as const satisfies Record<…, string>, so adding a value to the @workspace/bridge type without adding its key is a type error.

// apps/app/src/features/deals/explorer/sidebar-filters/pedagogy-filter.tsx
import { BRIDGE_TEACHING_MODE_KEY } from "@workspace/i18n-messages/bridge/keys";

const { t: tBridge } = useBridgeTranslation();
// …
label={tBridge(BRIDGE_TEACHING_MODE_KEY[mode])}

Available registries: BRIDGE_PLAYER_KEY, BRIDGE_SUIT_KEY, BRIDGE_RANK_KEY, BRIDGE_SPECIAL_BID_LONG_KEY, BRIDGE_SUIT_BID_LONG_KEY, BRIDGE_POSITION_KEY, BRIDGE_VULNERABILITY_KEY, BRIDGE_VULNERABILITY_PHRASE_KEY, BRIDGE_SCORING_KEY, BRIDGE_TEACHING_MODE_KEY.

Rotation entries

The rotation namespace holds the four compass directions (direction.N/E/S/W) in each language. ./rotation/entries turns them into the LanguageEntry table that the @workspace/bridge rotater uses. It adds French elision (de → d') and a guard so the French and Catalan "Est" does not match Est-ce, Est-il and similar forms. It also appends a language-independent N/E/S/W abbreviation entry.

import { buildAllRotationEntries } from "@workspace/i18n-messages/rotation/entries";

packages/training, apps/app and apps/admin build their rotaters from this table.

Adding a namespace

  1. Create src/locales/<namespace>/en.json (plus fr.json, and the other languages if the namespace is player-facing).
  2. Add index.ts exporting <camelCase>Messages.
  3. Re-export it from src/index.ts, add an XxxNamespace type in src/types.ts, and add a subpath in package.json exports if an app imports it directly.
  4. Register it in the consuming app's i18next ns list and in its Vite I18N_LOCALES config (see Consumption).