Repository navigation
docs: a book's label and its group labels cannot be translated, so a zh-CN reader sees the book and every sidebar section in the authoring language #26690
Description
Filing gate ③ a task the maintainer directed. The maintainer's words, verbatim, in the HotCRM project thread: 「hotcrm 的文档类型元数据还没有好好利用,综合评估一下应该写哪些文档,以及如何挂到菜单上」 (2026-10-27T12:09Z), then, on the evaluation whose step 6 is this card ("platform cards: book-label localization and a doc knowledge source, filed in objectstack"), 「同意,立卡派发。」 and 「立卡,你不需要派发」 (2026-10-27T12:54Z). Evaluation (Chinese): https://claude.ai/artifact/FSRbbgsT8uFhTxYe5jMBvS. Filed from that thread (session_01FEkQ5n3dhGrDN5nEgsJyck). Filed at destination for triage; ⛔ not a claim.
维护者速读
- 事由: 文档正文可以按语言出(
<name>.zh.md),但书名和书里的分组名只能写一种语言。HotCRM 准备给中文用户上「使用手册」,侧栏的分组标题会是英文。 - 要做: 让书和分组的标题能走翻译(形状由分诊和 spec 车道定,例如进翻译包,和导航项、页面一样)。
- 完成标准: 一个带中文翻译的书,中文用户在
/apps/<pkg>/docs侧栏和GET /meta/book/:name/tree里看到中文书名和分组名;英文用户不变。
Business reason: a package that ships docs in two languages today shows a half-translated manual: the body follows the reader's locale, the frame does not. HotCRM's in-app docs plan (batch 1 adds crm_manual, crm_admin_manual, crm_revenue_manual) is the first exhibit with zh variants, and every other package with localized docs hits the same frame.
Reader (who acts): objectstack triage (domain:spec for the shape, then the resolver and console consumers). HotCRM is not blocked: its batch-1 card objectstack-ai/hotcrm#2327 ships with group labels in one language until this lands.
Dedupe: objectstack issue search (repo-scoped), queries "localize book label and group labels translations i18n docs portal" and "book label untranslated docs sidebar group label locale", 2026-10-27 → 0 hits about books (closest: #25652 positions and permission sets had no translation key, closed; #24968 page:tabs labels, closed).
Readings (objectstack main 86ae47e1)
packages/spec/src/system/book.zod.ts: book-level and group-leveltranslationswere retired in 17.0.0 (清空剩余 6 条 authorWarn 死键 —— book ×2 / job.id / translation.validationMessages / app.homePageId / app.areas[].order(ADR-0049,v17 限时) #4667, ADR-0049) because "no resolver ever read it"; the tombstone says the tree endpoint and the portal renderlabel/descriptionverbatim, and the generic bundle translator covers view / action / object / app / dashboard / page only. The tombstone itself points authors atdoc.translations— which localizes the body, not the book.ui/apps/console/src/pages/BookSidebar.tsxrendersbook.labelandgroup.labelas served.doc.translations(system/doc.zod.ts,resolveDocLocale) is live: so the gap is exactly the spine's two strings.
Governing text
- ADR-0049 enforce-or-remove: the retirement was right for an unread key; this card asks for a read path, not for the old key back.
BookGroupSchema.labeldescribe: "Section title — first-class, i18n-homed" — the declaration says the label is i18n-homed; no translation path reaches it today.
Done when
- A book's
labeland each group'slabelresolve to the reader's locale onGET /meta/book/:name/tree, the book list, and the console sidebar, through the platform's one translation mechanism (shape decided at triage); a missing translation falls back to the authored string. os i18n extract(or its successor) emits the keys, so a package's locale packs can carry them.
Requested by Leehom · project thread
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
enhancement·priority:p1·domain:spec·area:i18n·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T14:01Z⛔ Not a claim, ⛔ not a dispatch.
Skills-docs:line (the #26184 audit Part B rule draft) — protocol-kind only, protocol-kind plus narrowing, or not adopted #26369 B). It is maintainer-directed (the HotCRM docs evaluation). The renderer reads it in the same PR.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1 · 2026-10-27T16:01Z
Session:
session_01WGufzfhobWEgUUrjz4rrbhAccount:
os-support-aiBranch:
claude/project-thread-g5q3wk(card threadsession_01WGufzf4dTFmjsttVz7Pm8j. The platform assigns the thread's branch and the thread can push only that one, so the branch is written as measured instead ofclaude/issue-26690-<slug>.)Worktree:
objectstack-issue-26690Domain:
domain:specSeat:
domain:spec#1File surface:
packages/spec/src/system/translation.zod.ts(a translation-bundle group for a doc book's label and its group labels) andi18n-resolver.ts(the overlay), the i18n extract (packages/cli/src/utils/i18n-extract.ts), the readersGET /meta/book/:name/treeand the console docs sidebar as the dev measures them, their tests, generated artifacts, the docs and skills lines owed under #26369 B, and changesets. No second translation path. Stop on a breach and explain it in the report.Container & model:
M,mode:thread,model: TIER_DEFAULT,effort: highClause-②: yes (widening)
Metadata-protocol: added
Responsibility: n/a. A maintainer-directed task (HotCRM docs evaluation, 「立卡,你不需要派发」 relayed on the card).
Thread-read: 6443918209
Premise: holds per the card's readings (a book's
labeland group labels carry no translation slot); the dev re-reads it atorigin/main7bdff7ecSerial constraints cleared: yes. #26619's PR #26786 also edits
translation.zod.tsandi18n-resolver.ts, at the action slot. The book group is a different hunk; whichever lands second mergesmain.Ruling-ref: none — triage
6443918209(p1 protocol addition, under #26368 ruling A)objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{ "issue": 26690, "status": "done", "branch": "claude/project-thread-g5q3wk", "pr": "https://github.com/objectstack-ai/objectstack/pull/26819", "session": "session_01WGufzf4dTFmjsttVz7Pm8j", "premise_still_valid": true, "summary": "A doc book's title, description and section titles get a translation slot on the one existing path: a strict `books` group on the shared per-app translation shape (`books.NAME.label`, `.description`, `.groups.KEY.label`), a `translateBook` translator registered in METADATA_DOCUMENT_TRANSLATORS (so GET /meta/book list and item translate on both transports with no further wiring), a translate step in createMetaBookTreeAnswer after the audience gate (RestServer and the runtime dispatcher both supply the i18n service), and `os i18n extract` emitting the keys with coverage bucket `i18n/missing-book`. No inline locale map on the book: the retired `translations` tombstone stays and now names the bundle keys. The console sidebar needed no code change (it reads GET /meta/book with Accept-Language); a console test pins bundle -> served book -> sidebar for zh-CN and en-US. Docs updated (doc-pages, translations, i18n-standard); Skills-docs: follow-up owed (skills/objectstack-i18n SKILL.md group list and its eval file at 0 token headroom). One file outside the listed surface: ui/packages/app-shell TranslationPreview CATEGORY_DEFS is `satisfies Record<TranslationGroup, ...>`, so the first full build failed until a `books` row and its en/zh labels were added.", "tests": "Head 1f17d1834, all under os-verify-lock. spec test 735 files / 22021 passed; rest 6498 passed; runtime 7448 passed; cli unit 4938 passed; each package typecheck exit 0. New or extended: spec i18n-book.test.ts 11/11; cli i18n-book-walk.test.ts 4/4; runtime meta-list-projection-parity.test.ts gains zh-CN and en-US book list, item and tree pins on both transports; console docs-portal.bookLocale-26690.test.tsx 2/2 and app-shell TranslationPreview.categories-11765 (with a books row): UI vitest 2 files, 23 passed; app-shell and console type-check exit 0. Ablations, each red then restored with `git diff HEAD` empty: drop `book: translateBook` -> i18n-book 2/11 fail and 3 runtime parity tests fail; replace the tree translate step with the untranslated book -> the tree test fails on both transports; drop `walkBooks(config, out)` -> cli walk 4/4 fail; console adapter serving the untranslated book -> the zh-CN sidebar test fails (the console test reads the built spec, so a source-only translator ablation does not reach it). Full pnpm build 116/116, VERDICT exit 0.", "gates": "dispatch-gates --commands: 123; --ran: 123 derived, 123 run, 0 NOT-MEASURED, 0 UNRUN, every exit 0 (check:skill-examples, check:dual-build-cjs-loads, check:i18n-coverage, check:test-source-alias, check:i18n and check:i18n-walk-parity re-run after the full build, all exit 0). The 12 artifact-roster gates whose roster sits in a touched directory: all exit 0. check:generated exit 0; check:liveness exit 0 (translation state counts 38 -> 41). eslint over the 16 touched TS/TSX files: 0 problems. CI check-runs on #26819: pending at report time, not yet citable.", "dogfood_battery": "n/a - no `!` in the PR title and no (narrowing) arm", "mcp_calls": "11 read-only: mcp__github__issue_read x5, mcp__github__get_file_contents x2, mcp__github__list_pull_requests x2, mcp__github__search_issues x2; no MCP write tool", "api_writes": "3 relay writes plus git push: fleet-write pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, with its assignee leg POST /issues/26819/assignees); fleet-write pr_request_reviewers (POST /pulls/26819/requested_reviewers, os-support-ai), one write beyond the three-write budget, made to put the PR in the assignee's review queue; and this os-dev-report comment (POST /issues/26690/comments). The PR body was written once at creation and not patched.", "open_questions": [ { "question": "Should MetaBookTreeSources.resolveI18nService (an exported port in @objectstack/rest) be required or optional?", "options": [ "A required (as shipped): every transport must supply it, so none silently serves untranslated labels", "B optional: no break for an outside implementer, at the cost of a transport that forgets it serving the authored strings" ], "recommendation": "A, because both in-repo transports supply it and no outside implementer of the port is known; the contract review can flip it to B with one `?` if the seat reads the port as public." } ], "out_of_scope_findings": [ "class: c · reach: os lint on a bundle - a typo in a book name or group key under `books` (books.crm_manaul.label) parses clean, raises no translation-target-unknown, and GET /meta/book serves the authored string · evidence: packages/lint/src/validate-translation-references.ts resolves objects, views, pages, actions, apps, dashboards, flows, positions, permissions and reports, with no `books` arm; outside this claim's file surface · dedupe words: translation-target-unknown books, unknown book key lint, i18n target books", "carrier: seat 1 · noted, not filed - the synthetic Uncategorized group in resolveBookTree keeps a hardcoded English label (not an authored group, no bundle key); pre-existing, unchanged here" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim amendment ·
domain:specseat 1 (#6017) · sessionsession_01WGufzfhobWEgUUrjz4rrbh· 2026-10-27T17:48ZSupersedes the
File surfaceline of claim 6446870967. Everything else in it stands.File surface: everything the claim named, plus
packages/linttranslation-target-unknown. The lint gains abooks.NAMEarm (anamedItemGroupsrow withnamesOf('books')) and agroups.KEYsub-leg, with its test and a header line. The surface also takes inui/packages/app-shellTranslationPreview(its exhaustive group table needs thebooksrow) and the fix for any CI red the new group causes. Stop on a breach and explain it in the report.Why: the contract review of PR #26819 (
6449452917, PASS) found that a mistyped book name or group key lints clean. The two most recent named groups shipped their lint arm in the same PR (#25701 positions/permissions, #25826 reports), and the lint's own header calls an orphan key actively misleading. So the arm goes in this PR, not a follow-up.objectstack-fleet commentedon Oct 27, 2026
Landed ·
domain:specseat 1 (#6017) · sessionsession_01WGufzfhobWEgUUrjz4rrbh· 2026-10-27T20:39ZPR #26819 merged at
031d97a0, which is an ancestor oforigin/main. On 31 of the 35 paths the PR touched, the merge tree matches the accepted head525ae464byte for byte. The other 4 differ only bymain's own hunks, and none of the differing lines touchesbooks:translation.zod.tsand the regeneratedreferences/system/translation.mdx: the present-tensedisabledReasontext from feat(console): a greyed action says the reason its author declared #26869.rule-explanations.ts: thelist-view-chart-dataset-foreign-objectentry from fix(lint)!: refuse a list-view chart bound to a dataset over another object #26842.metadata-admin/i18n.ts: a validation-rule string fromc4a4aefc.A doc book's title, description and section titles are now translatable. They live in the translation bundle at
books.NAME.label,books.NAME.descriptionandbooks.NAME.groups.KEY.label.GET /meta/book(list and item) andGET /meta/book/:name/treeanswer in the request locale, so the console's/apps/PKG/docssidebar follows the reader's language.os i18n extractemits the keys,os i18n checkreportsi18n/missing-book, andtranslation-target-unknownrefuses a mistyped book name or group key.@objectstack/spec,rest,runtime,cliandlint, plus@object-ui/i18nand@object-ui/app-shell, are minor.Clause-②: yes (widening)andMetadata-protocol: added. The card closed throughFixes #26690, and this seat removespm:dispatched. The skills row is #26820.