OSS-first docs

These docs teach the open system first: contracts, generated surfaces, runtimes, governance, and incremental adoption. Studio shows up as the operating layer on top, not as the source of truth.

AI index

Consumer i18n setup

Use ContractSpec i18n without losing ownership of translation contracts

Author translations as ContractSpec specs, resolve them through the framework-independent runtime, and project to adapters only when a host app needs them. This keeps web, server, mobile, CLI, and tests aligned on one source of truth.

Install the reusable packages

bun add @lssm-tech/lib.contracts-spec @lssm-tech/lib.translation-runtime

Use @lssm-tech/lib.contracts-spec/translations for catalogs and @lssm-tech/lib.translation-runtime for runtime resolution, formatting, diagnostics, snapshots, and optional adapters.

1. Translation contracts

Author TranslationSpec catalogs in or near the package that owns the copy. Keep meta.key stable, locale as BCP 47, placeholders documented, and owners explicit.

  • packages/libs/contracts-spec/src/translations
  • @lssm-tech/lib.contracts-spec/translations

2. Runtime resolution

Use one request/runtime instance to negotiate locale, apply fallback chains, resolve tenant/user overrides, format ICU messages, and collect diagnostics.

  • packages/libs/translation-runtime/src/runtime.ts
  • @lssm-tech/lib.translation-runtime

3. Optional adapters

Project a runtime snapshot to i18next only when a host app already uses i18next. Keep ContractSpec specs and snapshots canonical.

  • packages/libs/translation-runtime/src/adapters/i18next.ts
  • @lssm-tech/lib.translation-runtime/i18next

Define the contract first

The spec layer owns keys, locales, versions, owners, placeholders, fallback intent, and translator context. Do not make framework JSON files or i18next resources the canonical source.

import { defineTranslation } from "@lssm-tech/lib.contracts-spec/translations";

export const checkoutEn = defineTranslation({
  meta: {
    key: "checkout.messages",
    version: "1.0.0",
    domain: "checkout",
    owners: [{ team: "growth" }],
  },
  locale: "en-US",
  fallback: "en-US",
  messages: {
    "checkout.pay": {
      value: "Pay {amount, number, currency}",
      description: "Primary checkout submit label.",
      placeholders: [{ name: "amount", type: "currency" }],
    },
  },
});

Resolve at runtime

Create a runtime with requested locales, fallback locales, specs, and diagnostics. Server hosts should create one runtime per request; mobile hosts should pass device/user locales explicitly.

import { createTranslationRuntime } from "@lssm-tech/lib.translation-runtime";
import { checkoutEn, checkoutFr } from "./translations/checkout";

const runtime = createTranslationRuntime({
  defaultLocale: "en-US",
  requestedLocales: [requestLocale, "fr-FR", "en-US"],
  fallbackLocales: ["en-US"],
  specs: [checkoutEn, checkoutFr],
  onDiagnostic: (diagnostic) => reportI18nDiagnostic(diagnostic),
});

const label = runtime.t("checkout.pay", { amount: 42 });

Package-level helper pattern

For package-local copy, use createI18nFactory to expose a small typed helper while still registering real TranslationSpec catalogs.

import { createI18nFactory } from "@lssm-tech/lib.contracts-spec/translations";
import { packageEn, packageFr } from "./catalogs";

export const packageI18n = createI18nFactory({
  specKey: "my-package.messages",
  catalogs: [packageEn, packageFr],
});

export const t = packageI18n.create("fr-FR").t;

Production checklist

  • Define TranslationSpec catalogs before wiring UI strings into components.
  • Validate catalogs in tests so missing placeholders, invalid locales, and duplicate keys fail early.
  • Create a translation runtime per server request when tenant, workspace, team, user, or request locale can differ.
  • Serialize and hydrate the same runtime snapshot for SSR or streaming hosts; do not renegotiate locale during first client render.
  • Let React Native hosts detect locale and load required Intl polyfills before formatting ICU messages.
  • Use diagnostics as release evidence: missing keys, fallback usage, invalid snapshots, and adapter warnings should be visible in CI or observability.