## Problem
We have never decided what a localized README is for, and PR #1052 i…s the first change that forces the question.
Today the five localized files share one shape and the English file has another:
| File | Lines | `##` sections | Volatile tokens (dates, `N,NNN` counts) |
| :--- | ---: | ---: | ---: |
| `README.md` | 1049 | 18 | 12 |
| `README_zh.md` | 315 | 9 | 1 |
| `README_ja.md` | 305 | 9 | 1 |
| `README_es.md` | 242 | 9 | 0 |
| `README_ur.md` | 289 | 9 | 1 |
| `README_hi.md` | 306 | 9 | 1 |
| `README_zh.md` as proposed in #1052 | 961 | 18 | 12 |
So #1052 does not expand `README_zh.md` within the existing convention. It moves that one file onto the English file's shape and leaves its four siblings behind. Whichever way we go, the five should agree, and right now nothing written down says which way that is.
## The two positions
**Position A, mirror.** A localized README is a translation of `README.md`, section for section. A reader in any language gets the same document. This is what #1052 implements.
**Position B, abridged localized entry point.** A localized README covers what a reader needs to evaluate and install the product, links to the English docs for depth, and additionally carries content that has no English counterpart because it serves that locale only. This is what the five files are today, though by accident rather than by decision.
## Why this is not just a style preference
Position A copies the volatile content, and the volatile content is exactly what we cannot audit in a language we do not read.
`README_zh.md` as proposed carries `17,692` tests, `549` test files, `542` files in the tree at measurement time, the eight per-layer file counts (`328`, `35`, `24`, `21`, `21`, `13`, `107`, `18`), the measurement date `2026-09-15`, the timings `211` / `61` / `36` seconds, `bun 1.4.2`, and inside the twenty-six-engine paragraph `4.5 分钟对 3.3.22`, `2026-09-16 的一次修复`, `Cassandra 5.0.9` and `Garnet 2.1.5 (Redis 7.4.3)`.
Every one of those goes stale on a normal working day, in five files, with no guard watching. `scripts/readme-check.mjs` enforces three invariants only, and its own docblock says so: the engine name set is identical across all six files, every command in a localized install table appears verbatim in `README.md`'s, and every README carries the plain-HTTP `AUTH_COOKIE_SECURE` blockquote. Feature lists, counts, dates and measured numbers are outside its universe.
`reviews/todos.md:178` already records this failure happening once: PR #719 added a Command Palette bullet to `README.md`, nothing went red, and the translations have advertised one feature fewer ever since. That entry is still marked `unfiled`.
## Why this is not just "keep them short" either
The 315-line `README_zh.md` was already drifting, per the same `todos.md` entry. Brevity does not prevent drift on its own. What prevents it is translating only what a guard can check or what changes rarely. Shortness is the consequence of that rule, not the goal.
## The second question: localized-only content
`README_zh.md` on `main` carries a section with no English counterpart, and #1052 deletes it. `README_zh.md:233-248`:
```bash
# 南京大学镜像:把 ghcr.io 换成 ghcr.nju.edu.cn
docker pull ghcr.nju.edu.cn/libredb/libredb-studio:latest
# DaoCloud 镜像:在完整镜像名前加 m.daocloud.io/
docker pull m.daocloud.io/ghcr.io/libredb/libredb-studio:latest
# npx / npm:使用 npmmirror 源
npx --registry=https://registry.npmmirror.com @libredb/studio
```
plus the note that the SJTU mirror shut down in 2026-06 and a pointer to `dongyubin/DockerHub` for whichever mirrors are still serving.
Verified absent from the head blob of #1052: `ghcr.nju.edu.cn`, `m.daocloud.io` and `registry.npmmirror.com` return nothing.
Two smaller ones in the same class also go: the `## 文档` index with its statement that the deep docs are English only, and the line in `## 参与贡献` saying that issues and PRs written in Chinese are completely fine.
This content belongs in `README_zh.md` and nowhere else. GHCR is reachable from the `ja`, `es`, `ur` and `hi` audiences, and none of those four files carries mirror content today (confirmed: zero hits for all three domains in each). Putting it in `README.md` would be noise for most readers.
It also needs a different bar from the surrounding prose. Wrong prose confuses a reader; a wrong registry domain sends them to pull an image from somewhere we did not choose. The three domains above must be verifiable without reading Chinese, and a future PR that adds or changes a mirror line needs that line checked by hand.
`readme:check` is structurally blind to all of this. Its three invariants are all of the form "the localized file must not diverge from `README.md`", so content that has no English counterpart to diverge from is invisible to it. The guard needs a direction it does not currently have, not a wider version of the direction it has.
## Proposed answer, to be confirmed in this issue
Position B, made explicit, with a protected localized tail.
1. A localized README translates the stable spine: overview, why, quickstart, the engine table, the install command blocks, environment variables. These are the parts `readme:check` already guards or that change roughly once a release.
2. Volatile and sprawling sections are summarized in two or three sentences and linked, not translated: the testing section links to `docs/TOOLCHAIN.md`, the twenty-six-engine measurement paragraph links to `docs/providers/README.md`, the nine PaaS deployment recipes link to `docs/DISTRIBUTION.md`. In `README_zh.md` those three account for roughly 330 of the proposed 961 lines and for nearly all of its volatile tokens.
3. Localized-only sections are permitted, expected, and protected by the guard.
## Acceptance criteria
- [ ] This issue records the decision, A or B, in a comment, and `CONTRIBUTING.md` gains a short "localized READMEs" paragraph stating it so the next contributor does not have to guess.
- [ ] `scripts/readme-check.mjs` gains a fourth invariant enforcing localized-only content. Suggested mechanism: a `` marker comment immediately above a heading marks that heading's section as protected, and the guard fails when a marked section present on `main` is absent from the working tree. A marker is used rather than heading text because the heading text is in a language the guard cannot parse, which is the same reason `parseTables` already locates tables structurally.
- [ ] The marker is applied to the three `README_zh.md` sections named above once they are restored.
- [ ] The guard's docblock states the fourth invariant and why it points the opposite way from the other three, in the style of the existing three.
- [ ] Unit tests in `tests/unit/readme-check.test.ts` cover the new pure functions: a protected section present in both files passes, one removed from the localized file fails, one removed from `main` as well passes, and a marker on a heading that does not exist is an error rather than a silent pass. The repo gate is 100% line coverage, so the tests land in the same PR.
- [ ] `bun run readme:check` passes on all five localized files after the change.
- [ ] `reviews/todos.md:178` is updated from `unfiled` to point at this issue.
## Out of scope
Bringing `README_ja.md`, `README_es.md`, `README_ur.md` and `README_hi.md` up to whatever shape is decided. That is per-language follow-up work and needs a speaker of each. This issue decides the policy and builds the guard.
## Context
Raised by PR #1052, which is a good-quality translation from a first-time contributor and is blocked only on the deletions described above. The scope question is ours to answer, not theirs, and it should be settled here before they are asked to change anything.