From 9c248717daee3e1d349e0c6858e3d294e9df5da1 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 2 Nov 2026 21:28:52 +0000 Subject: [PATCH] docs(chord): messages and languages in the app kit decided --- packages/chord/docs/next/decisions.md | 23 +++++++++++++++++++++++ packages/chord/docs/next/design.md | 15 +++++++++++++++ packages/chord/docs/next/slices.md | 5 +++++ 3 files changed, 43 insertions(+) diff --git a/packages/chord/docs/next/decisions.md b/packages/chord/docs/next/decisions.md index d3b47e00a..20049a107 100644 --- a/packages/chord/docs/next/decisions.md +++ b/packages/chord/docs/next/decisions.md @@ -2373,6 +2373,29 @@ validation and copies the v2 kernel added around Delta. strings anyway). Known gaps, accepted until Node closes them: the child can signal the user's processes (`process.kill`) and allocate memory outside its heap. Cost: one more Node process per terminal session (measured by the build against the worker). Back to a worker only if Node ships per-worker permissions. +- **Messages and languages in the app kit (owner, 2026-11-03: "i18n proposal is a go"; proposal `docs/next/i18n/`):** + text people read is translated per viewer, never per server. (1) **Messages in code, per plugin:** + `defineMessages(namespace, { en: {...}, de: {...} })` in the app's shared API returns a typed `t`; the first catalog + defines the keys and argument types, every other catalog must match them (a missing key or a wrong argument is a + type error); values may be functions (plurals, parameters in each language's order); dates and numbers through + `Intl` helpers (`formatTime`, `formatNumber`) in the viewer's locale. Named keys (`newNote`), the English catalog the + source of truth. A plugin keeps its messages in one shared file (`messages.ts`) every facet that renders imports. + (2) **Languages are contributions:** a `Translations` registry (`{ namespace, locale, messages, override? }`) in the + shared API; `MessagesOf` types a translation; a missing key falls back to the locale's base (`pt-BR` to + `pt`), then to the first catalog, key by key, reported once; two contributions for one namespace and locale: the first + by plugin name wins, the other reported; a plugin's own catalogs are its built-in contributions, `override: true` + replaces one. Contributions are per host, so a translation plugin keeps its catalogs in a shared file and contributes + them from each facet that renders (web, tui; server and worker facets only when they render final text, an email or + a push notification). Catalogs never cross links (they hold functions). (3) **The locale is a user preference** + (kept per principal like the theme choice), defaulting to the browser's `navigator.languages` or the system locale + in the terminal, matched against the locales the catalogs offer; switching re-renders live (`useMessages(t)` on the + web, the scope's redraw in the terminal), nothing reloads; the kit sets `lang` and `dir` on the page's root + (right-to-left through logical CSS properties, the app's components). (4) **Servers and workers send keys, not + text:** `t.message(key, ...args)` builds typed data `{ namespace, key, params }` (a `Message<"notes">`) for object + state, notices, tool summaries and errors shown to people (a code plus parameters); each client renders it with + `t.render` in its own language (checked at runtime across a link: an unknown key shows the key). Never translated: + the model's prompts and answers, log lines, error codes. No translator file format now; an export/import tool to JSON + when someone needs it. Built after slice 11 (carry-over "After slice 11"). - **A plugin's npm dependencies are bundled; external only by opt-in (owner, 2026-10-29: "explicit opt in + (a)"):** a plugin's `dependencies` are installed into its own `node_modules` (`npm install --ignore-scripts`) and bundled into each facet that imports them (host packages, its `peerDependencies`, stay external and are passed in). On Node a diff --git a/packages/chord/docs/next/design.md b/packages/chord/docs/next/design.md index 69ebfd0aa..da87d32a7 100644 --- a/packages/chord/docs/next/design.md +++ b/packages/chord/docs/next/design.md @@ -2311,6 +2311,21 @@ Decision "Slice 11's points" and "Slice 11, P3" to "P9". the trusted launcher outside, confirming copy, open and attach on its own prompt); a component scope (`hold`, `watch`, `registry`, released on dispose, one `changed` callback for the app's redraw), no pi-tui dependency. +### Messages and languages (after slice 11) + +Decision "Messages and languages in the app kit". Text people read is rendered per viewer, never by a server. + +- **Messages in code:** `defineMessages(namespace, { en, de, ... })` per plugin, in a shared `messages.ts` every + rendering facet imports; typed `t(key, ...args)`, keys and signatures from the first catalog; functions for plurals + and word order; `formatTime`, `formatNumber` through `Intl` in the viewer's locale. +- **Translations** registry (`{ namespace, locale, messages, override? }`), per host: a translation plugin contributes + its shared catalog file from each facet that renders. Fallback key by key: the locale's base, then the first catalog + (reported once); conflicts: the first by plugin name wins. Catalogs never cross links. +- **The locale** is a per-principal preference (device default, live switch, `lang` and `dir` on the page). +- **Messages as data:** `t.message(key, ...args)` gives `{ namespace, key, params }` for state, notices and errors; + each client renders it with `t.render` (checked at runtime across a link). Prompts, model output, logs and error + codes are never translated. + ### Channels WebSocket byte channels with acknowledged windows, separate from object links, wait for a second user after the diff --git a/packages/chord/docs/next/slices.md b/packages/chord/docs/next/slices.md index a6472a274..808044528 100644 --- a/packages/chord/docs/next/slices.md +++ b/packages/chord/docs/next/slices.md @@ -201,6 +201,11 @@ deleted (the slice's spec and code are the record). Moved here from `carry-over. ### After slice 11 +- **Messages and languages in the app kit** (decision "Messages and languages in the app kit", proposal + `docs/next/i18n/`): `defineMessages`, `t.message`/`t.render`, `MessagesOf`, the `Translations` registry with + fallbacks and conflicts, the locale preference per principal (default from the device, live switch, `lang`/`dir` on + the page), `useMessages` and the terminal scope's redraw, `formatTime`/`formatNumber`; tests and an example (the + notes plugin in English and German, a French translation plugin, a server message rendered per viewer). - **Small examples** (owner, 2026-10-30: the 7c examples are complete but complex): once HTTP, workers, web and TUI exist, a pass of small, easy-to-read examples, each showing one thing a plugin author or app does.