docs(chord): messages and languages in the app kit decided

This commit is contained in:
Mario Zechner 2026-11-02 21:28:52 +00:00
parent 806cdd68e3
commit 9c248717da
3 changed files with 43 additions and 0 deletions

View file

@ -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<typeof t>` 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

View file

@ -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

View file

@ -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.