You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
console: the standalone /forms/:name route renders select option labels untranslated — a zh-CN user on an en-US browser reads Chinese field labels over English options (FormPage normalizeOptions skips the localizer) #26452
Found in the 18.2.0 wave-2 Tier 1 sweep (record above, which carries the full evidence chain) at framework 756220c4. Reproduced twice by the live runner and re-derived independently on a fresh boot.
What happens
On the console's standalone form route /_console/forms/OBJECT.form?recordId=ID&recordObject=OBJECT, a user who has chosen zh-CN as their language sees field labels and sections in Chinese, while every select option stays in English. The in-app overlay form (?form=ID on the object list) in the same session shows the options in Chinese. The translation bundle has the option labels.
Reproduction (stock showcase)
Boot the showcase. In a browser whose locale is en-US, sign in as admin@objectos.ai through the console form.
Set the user's language to zh-CN: avatar menu → Language → 中文(中国), or PATCH /api/v1/data/sys_user/USER_ID with {"locale":"zh-CN"} (answers 200).
GET /api/v1/i18n/translations/zh-CN carries objects.showcase_task.fields.status.options = backlog 待规划 · todo 待办 · in_progress 进行中 · in_review 评审中 · done 已完成, and the priority options.
Open /_console/forms/showcase_task.form?recordId=ANY_TASK_ID&recordObject=showcase_task and wait for the form to render.
Expected: status options 待规划/待办/进行中/评审中/已完成 and priority options 低/中/高/紧急, matching the zh labels on the page.
Actual: the labels read 标题*, 状态*, 优先级, but the selects read Backlog|To Do|In Progress|In Review|Done and Low|Medium|High|Urgent, and the comboboxes show In Review / High.
Control 1:/_console/apps/com.example.showcase/showcase_task?form=ID in the same session renders the options in Chinese.
Control 2: the same /forms URL in a zh-CN browser renders the options in Chinese. On this route, options get translated only when the server localizes the schema by Accept-Language, never from the language the user chose.
Mechanism
In ui/apps/console/src/components/FormPage.tsx, near line 848, the field mapping sets options: normalizeOptions(def.options) with no option-label translation. The neighbouring label, placeholder and helpText all go through the localizer. Per i18n.surface-matrix n1 this is a resolver FAIL: the spec declares the options group, and the bundle carries a translation for it.
Related, same route
The route's refusal message and its missing-recordId message are hard-coded in English.
Low priority: ?form=UNKNOWN_ID on the object list opens a blank edit form instead of a not-found state; the drawer handles an unknown recordId correctly with 未找到记录.
Done when
On every console form surface, option labels follow the user's chosen language, as field labels already do.
A pin covers the /forms route under a browser locale that differs from the user's language.
Claim: PM loop round 11 · 2026-10-27T05:26Z
Session: session_01SMps7wq7rBTk53e8KTSEup
Account: os-steve
Branch: claude/issue-26452-form-options-locale
Worktree: objectstack-issue-26452
Domain: domain:ui
Seat: domain:ui#2
File surface: ui/apps/console/src/components/InternalFormRoute.tsx and FormPage.tsx (the standalone form route's schema / option-label translation), the shared translator they should reuse if one exists, tests beside them, root .changeset/. ⛔ componentsform.tsx (seat 1). ⛔ locale packs under ui/packages/i18n unless the card needs a new key (then en + zh only, and say so), ui/.github/**, root ci.yml.
Container & model: M, mode:subagent, model: default tier, effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: a zh-CN user filling a record on the standalone form route | the i18n bundle's objects.*.fields.*.options labels | every select field on /_console/forms/*
Thread-read: none
Premise: holds 2026-10-27T05:26Z at origin/main00a4593ad7: the route is mounted from ui/apps/console/src/components/InternalFormRoute.tsx / FormPage.tsx; the card reproduces English option labels there while the in-app overlay form translates them.
Serial constraints cleared: no open PR touches InternalFormRoute.tsx or FormPage.tsx.
Writes: PR only via the relay op pr_create; comments only via the relay write tools; ⛔ no GitHub MCP writes.
Seat's route: find where the overlay form (?form=ID) translates option labels and make the standalone route use the same path, not a second translator. Pin red-first: zh-CN bundle with option labels, standalone route renders them translated; labels without a bundle entry keep their authored text. Fixes #26452.
{
"issue": 26452,
"status": "done",
"branch": "claude/issue-26452-form-options-locale",
"pr": "https://github.com/objectstack-ai/objectstack/pull/26584",
"session": "session_01SMps7wq7rBTk53e8KTSEup",
"premise_still_valid": true,
"summary": "The overlay form translates nothing on the client: ObjectForm's fromObjectSchema hands field.options through as served, and the server localizes /meta/object by the request's Accept-Language, which the console's authenticated fetch (SchemaRendererContext.apiFetch) stamps with the session language. FormPage read the view, schema and record with a bare fetch, so options followed the browser's language. The internal route now sends every API request through SchemaRendererContext.apiFetch (falling back to bare fetch with no provider); the public /f/:slug route is unchanged. No second translator, no locale-pack key, InternalFormRoute.tsx untouched.",
"tests": "Pin ui/apps/console/src/components/FormPage.optionLabelLocale-26452.test.tsx (real createAuthenticatedFetch as host apiFetch, html lang zh-CN, stub server localizing by Accept-Language). RED at 1768fd79 (pin only): 2 failed, AssertionError expected 'In Review' to contain '评审中' / expected 'Done' to contain '已完成' (run unlocked, maxWorkers=1, declared narrowing after a long lock queue). GREEN at ff64ab6a: 2 passed. At head f0294fa8 (after merging origin/main) under the lock, VERDICT command-exit 0: closure build @object-ui/console^... ok; console tests importing FormPage/InternalFormRoute: Test Files 42 passed (42), Tests 415 passed (415); pnpm --filter @object-ui/console type-check exit 0 (tsconfig includes src, so the pin is type-checked). The red-before-fix run is the ablation. Live showcase repro: NOT MEASURED, reason: showcase boot + console build + browser not cheap under the shared lock queue.",
"gates": "Local at f0294fa8: node scripts/pm/dispatch-gates.mjs --commands derived 33; all 33 run, each exit 0 (check:query-options-erasure and check:type-check-debt hit a 400s timeout on a first pass and were re-run to exit 0; ui scripts vitest + spec check:duration-unit-keys ran under the lock: 178 files passed, 2 skipped). --ran: 33 derived, 33 run, 0 NOT-MEASURED (derived zero, all with exit codes). node scripts/check-sdui-manifest.mjs exit 0 (intact, 106 components; no registry contract change, not regenerated). CI on f0294fa8 at report time: Governed Surface Queue Guard=success, No other open PR may claim the same issue=success, all others in_progress.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls + POST issues/26584/assignees, run 40477066470); this os-dev-report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/26452/comments). git push not counted.",
"open_questions": [
{
"question": "Should the public /f/:slug route also read over the host's authenticated fetch so a signed-in visitor's chosen language reaches the server?",
"options": [
"A leave it on the bare fetch (this PR): anonymous visitors are unaffected; a signed-in visitor with a non-browser language still sees browser-language options there",
"B move it onto SchemaRendererContext.apiFetch: language fixed for that case too, but the anonymous resolver and submit start receiving Bearer and X-Tenant-ID from signed-in visitors"
],
"recommendation": "A, because the card's surfaces are the internal routes and B changes what the anonymous submit sees, which needs its own look"
},
{
"question": "The seat's pin wording described a client bundle lookup; the overlay's path is server localization by request language, so the pin models the server and asserts the channel. Acceptable?",
"options": [
"A yes (this PR)",
"B add a client translateOptions call as well"
],
"recommendation": "A, because B would be the second translation path the card forbids"
}
],
"out_of_scope_findings": [
"carrier: #26452 (card's own Related notes) · the route's English-only refusal and missing-recordId messages, and ?form=UNKNOWN_ID opening a blank edit form — noted in PR Acceptance notes, not filed"
]
}
Status: the standalone form route (/forms/:name) talks to the API over the console's authenticated fetch, so option labels follow the session language like the in-app form; the public /f/:slug route is unchanged. pm:dispatched removed.
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
bug·priority:p1·domain:ui·area:i18n·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T04:54Z⛔ Not a claim, ⛔ not a dispatch.
i18n.surface-matrixn1, QA run · release-18.2.0-w2-tier1-b (80/80) · 756220c4 · 2026-10-27 · 15 PASS / 64 PARTIAL / 1 FAIL / 0 BLOCKED / 0 NOT-RUN #26450). Found by the 18.2.0 wave-2 sweep ([sweep] v18.2.0 release test sweep #25807). The standalone/forms/:nameroute skips the localizer for option labels, while the overlay form translates them. 「发版本优先的都p1」: fix it before the cut, or the maintainer accepts it as a known gap. Route it through the same localizer the overlay form uses. ⛔ No second translation path.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 11 · 2026-10-27T05:26Z
Session:
session_01SMps7wq7rBTk53e8KTSEupAccount:
os-steveBranch:
claude/issue-26452-form-options-localeWorktree:
objectstack-issue-26452Domain:
domain:uiSeat:
domain:ui#2File surface:
ui/apps/console/src/components/InternalFormRoute.tsxandFormPage.tsx(the standalone form route's schema / option-label translation), the shared translator they should reuse if one exists, tests beside them, root.changeset/. ⛔componentsform.tsx(seat 1). ⛔ locale packs underui/packages/i18nunless the card needs a new key (then en + zh only, and say so),ui/.github/**, rootci.yml.Container & model:
M,mode:subagent,model: default tier,effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
a zh-CN user filling a record on the standalone form route|the i18n bundle's objects.*.fields.*.options labels|every select field on /_console/forms/*Thread-read: none
Premise: holds 2026-10-27T05:26Z at
origin/main00a4593ad7: the route is mounted fromui/apps/console/src/components/InternalFormRoute.tsx/FormPage.tsx; the card reproduces English option labels there while the in-app overlay form translates them.Serial constraints cleared: no open PR touches
InternalFormRoute.tsxorFormPage.tsx.Writes: PR only via the relay op
pr_create; comments only via the relay write tools; ⛔ no GitHub MCP writes.?form=ID) translates option labels and make the standalone route use the same path, not a second translator. Pin red-first: zh-CN bundle with option labels, standalone route renders them translated; labels without a bundle entry keep their authored text.Fixes #26452.objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{ "issue": 26452, "status": "done", "branch": "claude/issue-26452-form-options-locale", "pr": "https://github.com/objectstack-ai/objectstack/pull/26584", "session": "session_01SMps7wq7rBTk53e8KTSEup", "premise_still_valid": true, "summary": "The overlay form translates nothing on the client: ObjectForm's fromObjectSchema hands field.options through as served, and the server localizes /meta/object by the request's Accept-Language, which the console's authenticated fetch (SchemaRendererContext.apiFetch) stamps with the session language. FormPage read the view, schema and record with a bare fetch, so options followed the browser's language. The internal route now sends every API request through SchemaRendererContext.apiFetch (falling back to bare fetch with no provider); the public /f/:slug route is unchanged. No second translator, no locale-pack key, InternalFormRoute.tsx untouched.", "tests": "Pin ui/apps/console/src/components/FormPage.optionLabelLocale-26452.test.tsx (real createAuthenticatedFetch as host apiFetch, html lang zh-CN, stub server localizing by Accept-Language). RED at 1768fd79 (pin only): 2 failed, AssertionError expected 'In Review' to contain '评审中' / expected 'Done' to contain '已完成' (run unlocked, maxWorkers=1, declared narrowing after a long lock queue). GREEN at ff64ab6a: 2 passed. At head f0294fa8 (after merging origin/main) under the lock, VERDICT command-exit 0: closure build @object-ui/console^... ok; console tests importing FormPage/InternalFormRoute: Test Files 42 passed (42), Tests 415 passed (415); pnpm --filter @object-ui/console type-check exit 0 (tsconfig includes src, so the pin is type-checked). The red-before-fix run is the ablation. Live showcase repro: NOT MEASURED, reason: showcase boot + console build + browser not cheap under the shared lock queue.", "gates": "Local at f0294fa8: node scripts/pm/dispatch-gates.mjs --commands derived 33; all 33 run, each exit 0 (check:query-options-erasure and check:type-check-debt hit a 400s timeout on a first pass and were re-run to exit 0; ui scripts vitest + spec check:duration-unit-keys ran under the lock: 178 files passed, 2 skipped). --ran: 33 derived, 33 run, 0 NOT-MEASURED (derived zero, all with exit codes). node scripts/check-sdui-manifest.mjs exit 0 (intact, 106 components; no registry contract change, not regenerated). CI on f0294fa8 at report time: Governed Surface Queue Guard=success, No other open PR may claim the same issue=success, all others in_progress.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls + POST issues/26584/assignees, run 40477066470); this os-dev-report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/26452/comments). git push not counted.", "open_questions": [ { "question": "Should the public /f/:slug route also read over the host's authenticated fetch so a signed-in visitor's chosen language reaches the server?", "options": [ "A leave it on the bare fetch (this PR): anonymous visitors are unaffected; a signed-in visitor with a non-browser language still sees browser-language options there", "B move it onto SchemaRendererContext.apiFetch: language fixed for that case too, but the anonymous resolver and submit start receiving Bearer and X-Tenant-ID from signed-in visitors" ], "recommendation": "A, because the card's surfaces are the internal routes and B changes what the anonymous submit sees, which needs its own look" }, { "question": "The seat's pin wording described a client bundle lookup; the overlay's path is server localization by request language, so the pin models the server and asserts the channel. Acceptable?", "options": [ "A yes (this PR)", "B add a client translateOptions call as well" ], "recommendation": "A, because B would be the second translation path the card forbids" } ], "out_of_scope_findings": [ "carrier: #26452 (card's own Related notes) · the route's English-only refusal and missing-recordId messages, and ?form=UNKNOWN_ID opening a blank edit form — noted in PR Acceptance notes, not filed" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Landed · seat 2 · 2026-10-27T10:24Z
fd973515, headf0294fa8(+238/−14, seat record on #26584)origin/maingit merge-base --is-ancestorexit 0)gh-readonly-queue/main/pr-26584-*: none left (consumed)Fixes #26452on #26584Status: the standalone form route (
/forms/:name) talks to the API over the console's authenticated fetch, so option labels follow the session language like the in-app form; the public/f/:slugroute is unchanged.pm:dispatchedremoved.