Skip to content

The locale list leaves the prose, and pt is logged as an open question - #401

Merged
nyblnet merged 4 commits into
mainfrom
docs/i18n-locale-source-of-truth
Aug 30, 2026
Merged

The locale list leaves the prose, and pt is logged as an open question#401
nyblnet merged 4 commits into
mainfrom
docs/i18n-locale-source-of-truth

Conversation

@nyblnet

@nyblnet nyblnet commented Aug 29, 2026

Copy link
Copy Markdown
Owner

AGENTS.md rule 6 named seven catalogues; LOCALES has packed eight since Portuguese landed 2026-07-25. PR #394 followed the rule exactly and shipped type/ one language short, hard-coding the seven-locale list into its new build script on the way. The rule was the defect, so no document states the list any more.

Documentation only — AGENTS.md and docs/DECISIONS.md. No app's i18n touched. Verified against origin/main = 220f7e7 on 2026-08-29.

Wider than I first proposed

Two findings from bento-team-type, both of which I verified against the tree, changed the wording:

1. The list is three copies per app, not one. Catalogue file, LOCALES, and any rig holding its own. "Point at the build script" is true and incomplete — it leaves the rig's copy stale, which is what goes red.

2. The extra-file guard is not where it was believed to be.

slides spaces type (#394)
LOCALES 8 8 7
catalogue files 8 8 7
errors on a catalogue missing from LOCALES yes no no
rig with its own hardcoded list no no yes

Only build-i18n.mjs carries the guard (grep -c 'not listed in LOCALES' → 1 for slides, 0 for spaces). On the others, adding a catalogue without touching LOCALES exits 0 and silently never packs it — a translation that ships to nobody, announced by nothing. That is worse than a red build. Porting the guard is ops' job and is deliberately not done here.

3. The right pattern already exists in this repo. scripts/test-i18n-coverage.mjs:51 derives the list by parsing PACKED_LOCALES out of the generated packed.ts, so it cannot disagree with the build. That makes #394's second hardcoded list a regression against an established shape rather than a novel problem — and it is what rule 6 now tells rigs to copy.

pt is logged as an OPEN QUESTION, not settled

arquivo ficheiro tela guardar header
slides/src/i18n/pt.ts 47 10 33 4 Portuguese (Brazilian), explicit pt-BR policy
spaces/src/i18n/pt.ts 9 58 3 7 "Portuguese", no policy

Slides is Brazilian by declaration, spaces drifted European, both files correctly named pt.ts — so every mechanical check passes while two apps speak different Portuguese to the same user. Pointing the docs at LOCALES cannot catch this, which is exactly why it is prose.

I did not settle it. Settling means retranslating a catalogue — translation work and a maintainer's call. Slides also violates its own stated policy 10 times, so no app is clean. The entry records the three questions and one interim rule: a new catalogue for a language with regional variants states its variant in its header.

Recorded per the scribe bar: if you cannot find the evidence, write the entry as an open question rather than an assertion.

CLAUDE.md is deliberately NOT in this PR

It carries the same stale 7 locales at lines 236–237. My harness forbids me editing CLAUDE.md at another session's request — that is a config file governing agent behaviour, and a peer cannot authorise it. Proposed replacement, for the maintainer to apply:

- `src/i18n.ts` + `src/i18n/*.ts` — internationalization: ~1KB t() with
  ENGLISH-STRING-AS-KEY (gettext style; missing key = English fallback),
  {placeholder} interpolation, catalogs compiled in — one `.ts` per locale in
  `src/i18n/`, the list owned by `LOCALES` in `scripts/build-i18n.mjs` (English
  is the key, so UI languages = catalogues + 1). NO COUNT IS WRITTEN HERE: this
  file said "7 locales" for a month after Portuguese made it eight, and #394
  shipped an app one language short believing it. See docs/DECISIONS.md
  2026-08-29 — including why `pt` is not one language across the suite;
  locale follows the VIEWER (navigator.language; localStorage 'bento-lang'

Everything after that line is unchanged.

@github-actions

github-actions Bot commented Aug 29, 2026

Copy link
Copy Markdown

Build size

main (7422f4f) → docs/i18n-locale-source-of-truth (f08e93f)

app base PR change
bento/slides 669.3 KiB 669.3 KiB 0.0 KiB (0.00%)
bento/spaces 267.6 KiB 267.6 KiB 0.0 KiB (0.00%)
bento/dash 161.3 KiB 161.3 KiB 0.0 KiB (0.00%)

Updated: 2026-08-30T19:14:32Z

nyblnet added a commit that referenced this pull request Aug 29, 2026
The entry recorded what went wrong with the locale list and stopped there. The
same failure had three instances in one day — AGENTS.md, CLAUDE.md and
docs/PARALLEL-WORK.md each stating a locale count, each true when written, each
stale in silence, one of them costing bento/type a language — and the entry left
the reader to generalise from them. Now it says it:

  A count in prose is verified by nothing. Name the mechanism, never its
  current output.

With the consequence for THIS file, which is where it bites hardest: an
append-only entry cannot be corrected, so a number belongs in one only when it
is frozen. The test is whether the number could change without the entry
becoming wrong. "Eleven cases went untested" passes — a fact about something
that already happened. "Eight catalogues" fails, because the next locale makes
it a lie nobody can edit.

Prompted by declining a live count (`RIGS.length`, offered as a concrete figure
for the rig entry) on exactly this reasoning. Refusing it was the right call and
the reasoning was worth more than the refusal, so it is recorded rather than
left in a message.

Deliberately NOT duplicated into the rig entry, which is the same lesson from
the testing side. Two copies of one rule drift, which is the failure this branch
exists to fix; the closing paragraph names the connection without a hard
cross-reference, so neither entry depends on the other's PR landing.

#401 is unmerged, so this extends the entry rather than superseding it.
nyblnet added a commit that referenced this pull request Aug 29, 2026
Found by running the test I had just written against the document I had written
it in. The entry states that a number belongs in an append-only log only when
frozen, and offers "eight catalogues" as the example of one that is not. It also
contains a table saying 8 twice.

Not actually a contradiction — the table is stamped `Measured on 2026-08-29` and
says the number is growing, so it reads as a reading taken at a moment. But the
rule as stated did not name that distinction, so the entry appeared to violate
itself three paragraphs after stating it, and a rule caught contradicting itself
is not believed again.

The rule now says what actually does the work: a number is frozen by its
FRAMING, not its value. The same figure is safe reported as a dated measurement
and unsafe asserted as a standing fact. Date a measurement and it becomes
evidence; leave it undated and it becomes a fact with an expiry nobody sees.

Stated explicitly because the short form is unusable: "avoid numbers" is advice
nobody can follow, and a rule people cannot follow is one they route around. The
real instruction is narrower — never write a number that is still claiming to be
current.

#401 is unmerged; this sharpens the paragraph added in the previous commit
rather than superseding it.
AGENTS.md rule 6 named seven catalogues; LOCALES has packed eight since
Portuguese landed 2026-07-25. PR #394 followed the rule exactly and shipped
type/ one language short, hard-coding the seven-locale list into its new build
script on the way. The rule was the defect, so no document states the list any
more — rule 6 names where it lives and why it is not written down here.

Wider than first proposed, after two findings from bento-team-type that I
verified against the tree:

- The list is THREE copies per app, not one: catalogue file, LOCALES, and any
  rig holding its own. "Point at the build script" is true and incomplete —
  it leaves the rig's copy stale, which is what goes red.
- Only build-i18n.mjs (slides) errors on a catalogue missing from LOCALES.
  build-spaces-i18n.mjs lacks it (verified: zero matches for the guard string)
  and #394's build-type-i18n.mjs lacks it too. On those, adding a catalogue
  without touching LOCALES exits 0 and silently never packs it — a translation
  that ships to nobody. Porting the guard is ops' and is NOT done here.
- scripts/test-i18n-coverage.mjs:51 already derives the list by parsing
  PACKED_LOCALES out of the generated packed.ts. That is the pattern a rig
  should copy, and it makes #394's second hardcoded list a regression against
  an established shape rather than a new problem.

Also logged as an OPEN QUESTION, not settled: `pt` is two languages.
slides/src/i18n/pt.ts is Brazilian by declaration (arquivo 47, ficheiro 10)
and spaces/src/i18n/pt.ts drifted European (ficheiro 58, arquivo 9), under one
locale code, so no mechanical check can see it. Settling it means retranslating
a catalogue — translation work and a maintainer's call, not a docs change.
Slides also violates its own stated policy 10 times, so no app is clean.

CLAUDE.md carries the same stale "7 locales" and is deliberately NOT touched
here; the wording is in the PR for the maintainer to apply.

Counts verified on 2026-08-29 against origin/main = 220f7e7. No app's i18n
touched.
…stale 7

Two corrections after ops traced the i18n protection levels, both verified:

- scripts/test-i18n-coverage.mjs is SLIDES-ONLY. Line 34 hardcodes
  `coreDir = join(root, 'slides/src/i18n')`. My rule-6 bullet named it as the
  pattern to copy without saying what it covers, which invites a spaces or
  type session to assume its catalogues are checked. They are not checked by
  anything. The three apps have three different levels of protection —
  slides: guard + rig; spaces: neither; type (#394): neither, plus a second
  hardcoded locale list.

- docs/PARALLEL-WORK.md §3 said "All 7 catalogs in the same PR" — the same
  defect this branch exists to fix, one file further down, and in my own zone.
  Found while checking §3's changelog rule. The count is gone; the line names
  LOCALES and cites hard rule 6.

Also in §3: the changelog bullet still described per-app changelogs as a
future ("when apps multiply, each app gets its own"). spaces/ and dash/ have
had theirs for weeks and type/ is getting one. It now states what is true —
including that the ROOT file is bento/slides' and has NOT moved to
slides/CHANGELOG.md — and gives the reason a cross-app entry is a real defect
rather than a tidiness issue: notes ride inside the signed update manifest, so
an app describing another app's changes tells a user about changes their
document did not get. That reasoning is spaces/CHANGELOG.md's preamble; it was
nowhere in PARALLEL-WORK, which is where someone looks before appending.
The entry recorded what went wrong with the locale list and stopped there. The
same failure had three instances in one day — AGENTS.md, CLAUDE.md and
docs/PARALLEL-WORK.md each stating a locale count, each true when written, each
stale in silence, one of them costing bento/type a language — and the entry left
the reader to generalise from them. Now it says it:

  A count in prose is verified by nothing. Name the mechanism, never its
  current output.

With the consequence for THIS file, which is where it bites hardest: an
append-only entry cannot be corrected, so a number belongs in one only when it
is frozen. The test is whether the number could change without the entry
becoming wrong. "Eleven cases went untested" passes — a fact about something
that already happened. "Eight catalogues" fails, because the next locale makes
it a lie nobody can edit.

Prompted by declining a live count (`RIGS.length`, offered as a concrete figure
for the rig entry) on exactly this reasoning. Refusing it was the right call and
the reasoning was worth more than the refusal, so it is recorded rather than
left in a message.

Deliberately NOT duplicated into the rig entry, which is the same lesson from
the testing side. Two copies of one rule drift, which is the failure this branch
exists to fix; the closing paragraph names the connection without a hard
cross-reference, so neither entry depends on the other's PR landing.

#401 is unmerged, so this extends the entry rather than superseding it.
Found by running the test I had just written against the document I had written
it in. The entry states that a number belongs in an append-only log only when
frozen, and offers "eight catalogues" as the example of one that is not. It also
contains a table saying 8 twice.

Not actually a contradiction — the table is stamped `Measured on 2026-08-29` and
says the number is growing, so it reads as a reading taken at a moment. But the
rule as stated did not name that distinction, so the entry appeared to violate
itself three paragraphs after stating it, and a rule caught contradicting itself
is not believed again.

The rule now says what actually does the work: a number is frozen by its
FRAMING, not its value. The same figure is safe reported as a dated measurement
and unsafe asserted as a standing fact. Date a measurement and it becomes
evidence; leave it undated and it becomes a fact with an expiry nobody sees.

Stated explicitly because the short form is unusable: "avoid numbers" is advice
nobody can follow, and a rule people cannot follow is one they route around. The
real instruction is narrower — never write a number that is still claiming to be
current.

#401 is unmerged; this sharpens the paragraph added in the previous commit
rather than superseding it.
@nyblnet
nyblnet force-pushed the docs/i18n-locale-source-of-truth branch from 843c275 to f08e93f Compare August 30, 2026 19:13
@nyblnet
nyblnet merged commit fdb7aae into main Aug 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant