Skip to content

fix(console): the standalone /forms route shows select option labels in the language the user chose - #26584

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-26452-form-options-locale
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-26452-form-options-locale

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #26452
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:独立表单路由 /forms/:name(及 /apps/:appName/forms/:name)改走控制台已认证的 fetch 读写 API,下拉选项标签随用户所选语言显示。
  • 为什么改:该路由用裸 fetch 读对象 schema,服务端按浏览器的 Accept-Language 本地化选项;应用内弹窗表单走的通道带会话语言,所以同一会话两处语言不一致(p1,发版扫描 FAIL)。
  • 风险与代价(含回滚):内部路由的请求从此带上 Bearer、X-Tenant-ID、Accept-Language,与弹窗表单一致;公开表单 /f/:slug 不变。回滚即 revert 本 PR。
  • 席位意见:
  • 你要做的:无需操作;席位复核后按常规入队。

Summary

On the standalone internal form route, a user whose chosen language differs from the browser's saw field labels in their language and every select option in the browser's. The in-app overlay form (?form=ID) in the same session showed the options translated.

Root cause

The overlay (ModalForm → ObjectForm) does not translate option labels on the client. Its field builder (fromObjectSchema in @object-ui/plugin-form) hands field.options to the widget as served. The translation happens on the server: the object schema is read over the console's authenticated fetch (createAuthenticatedFetch, published to the renderers as SchemaRendererContext.apiFetch). That fetch stamps Accept-Language with the session's UI language (the lang attribute of the html element), and the server localizes /meta/object, option labels included, in that language.

FormPage read /meta/view, /meta/object and the record with a bare fetch, so the request carried the browser's language. Field labels still came out translated because they resolve on the client through the bundle (fieldLabel); options have no client lookup on either form surface, so they followed the browser. This matches the card's Control 2: a zh-CN browser already got Chinese options on this route.

Fix

ui/apps/console/src/components/FormPage.tsx: in internal mode, every API request (the form view, object schema and record reads, and the create/update submit) goes through SchemaRendererContext.apiFetch, the host's authenticated fetch the console shell publishes. With no provider above the route it falls back to the bare fetch, as before. The public anonymous route /f/:slug keeps the bare fetch.

No option-label translator is added. The route now takes the channel the overlay takes, so the server's one resolver answers both surfaces. An option the bundle does not translate keeps its authored label, because that is the server's fallback.

InternalFormRoute.tsx is unchanged: both internal mounts (InternalFormRoute and InAppFormRoute) render FormPage mode="internal" inside the console shell's SchemaRendererProvider.

Tests

New pin ui/apps/console/src/components/FormPage.optionLabelLocale-26452.test.tsx. It mounts /forms/:name in edit mode inside a SchemaRendererProvider whose apiFetch is the real createAuthenticatedFetch({ sameOriginOnly: true }), with the html element's lang set to zh-CN and a stub server that localizes /meta/object by the request's Accept-Language.

  • Leg 1: the status and priority comboboxes read the zh option labels (评审中, 高), and the schema read carried zh-CN.
  • Leg 2: an option with no zh translation (urgent) keeps its authored label Urgent.

Red first, at 1768fd79 (pin only, no fix): 2 of 2 failed, AssertionError: expected 'In Review' to contain '评审中' and expected 'Done' to contain '已完成'. Green at ff64ab6a (fix): 2 of 2 passed.

At head f0294fa8 (after merging origin/main), under the shared verify lock (VERDICT command-exit 0):

  • closure build pnpm --filter "@object-ui/console^..." build: ok;
  • every console test file that imports FormPage or InternalFormRoute (FormPage.*, formPageRequiredMarker, internalFormShell, formViewFeaturesRootRefused, App.homePermissions, App.docsPortalLazy, App.uploadAltitude, createdRecordPath, submitRedirect, OAuthConsentPage.agent-scopes): Test Files 42 passed (42), Tests 415 passed (415);
  • pnpm --filter @object-ui/console type-check: exit 0. The console tsconfig includes src, so the new test is type-checked.

Live showcase reproduction: NOT MEASURED. Booting the showcase plus a console build and a browser on the shared box was not cheap under the verify-lock queue. The pin models the server's measured behaviour (the card's Control 2) with the real host fetch.

Gates

Local, at f0294fa8: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran reports 33 derived, 33 run, 0 NOT-MEASURED, and every one exited 0. That includes node scripts/check-sdui-manifest.mjs (intact, 106 components; no registry contract changed, so it was not regenerated) and pnpm --dir ui exec vitest run scripts/ (178 passed, 2 skipped). CI runs the rest.

Acceptance notes

  • The public route /f/:slug still reads over the bare fetch. For an anonymous visitor the UI language is the detected browser language, so nothing diverges. A signed-in visitor who chose another language would still see options in the browser's language there. Moving that route onto a session-carrying channel changes what the anonymous resolver and submit see, so it is left out of this change.
  • The card's two related notes (the route's English-only refusal and missing-recordId messages, and ?form=UNKNOWN_ID opening a blank edit form) are not addressed here.

Generated by Claude Code

… authenticated fetch, so option labels follow the chosen language

/forms/:name read the object schema with a bare fetch, so the server
localized option labels by the browser's Accept-Language. The internal
route now takes SchemaRendererContext.apiFetch, the channel the in-app
record form reads the schema over, which sends the session language.

Claude-Session: https://claude.ai/code/session_01SMps7wq7rBTk53e8KTSEup
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么:独立表单路由 /forms/:name(及 /apps/:appName/forms/:name)改走控制台已认证的 fetch 读写 API,下拉选项标签随用户所选语言显示,与应用内弹窗表单一致。
为什么改:该路由用裸 fetch 读对象 schema,服务端按请求的 Accept-Language 本地化选项,于是选项跟着浏览器语言走;弹窗表单的通道带会话语言,同一会话两处语言不一致(#26452,p1)。
风险与回滚:内部路由的请求从此带上 Bearer、X-Tenant-ID、Accept-Language,与弹窗表单相同;没有宿主 provider 时退回裸 fetch;公开表单 /f/:slug 不变。回滚 = revert 本 PR。
先看:ui/apps/console/src/components/FormPage.tsx 中 FormPage 的 channel 绑定 → apiFetch(path, init, channel) → 新测试 FormPage.optionLabelLocale-26452.test.tsx。
两分钟验证:在 ui/ 下运行 pnpm exec vitest run apps/console/src/components/FormPage.optionLabelLocale-26452.test.tsx,预期 2 个用例全过。
需要你做的:无,席位落地。
席位意见:ACCEPT。两个开放问题席位都裁 A:/f/:slug 保持裸 fetch(匿名提交不应收到 Bearer 与租户头);测试断言请求通道而非浏览器端翻译包,因为选项翻译本就在服务端,再加一条客户端翻译就是卡片禁止的第二条路径。PR 正文速读标签不全,以本记录为准。

席位核对

seat 2 · 2026-10-27T09:13Z · head f0294fa8 (#26452)

item reading
form 3 files, +238/−14: FormPage.tsx internal mode sends view, schema, record and submit through SchemaRendererContext.apiFetch (bare fetch with no provider; public /f/:slug unchanged); new 2-cell pin with the real createAuthenticatedFetch, page language zh-CN and a server stub that localizes by Accept-Language; root .changeset/ patch on @object-ui/console
premise measured plugin-form's fromObjectSchema passes field.options through as served; the server localizes /meta/object by Accept-Language; the overlay's fetch stamps the session language, FormPage's bare fetch sent the browser's
gates (check-runs on f0294fa8) 45 success, 9 skipped, 0 failing (Test UI 16/16, Build & E2E UI, Type Check UI and debt ledger, Lint UI, Check PR Size, governed guard all green). Local: red 2/2 at 1768fd79 (expected 'In Review' to contain '评审中'), green 2/2 at ff64ab6a; at head under the lock 42 console files / 415 tests and console type-check exit 0; check-sdui-manifest exit 0; 33 of 33 derived gates run, all exit 0
accepted deviations red/green pin legs ran outside the lock with one worker (lock queue over an hour), broader runs under it; live showcase reproduction not measured; InternalFormRoute.tsx untouched
out of scope English-only refusal / missing-recordId messages and the blank edit form on an unknown ?form= id (noted in the PR's Acceptance notes; not filed)
landing not governed (check-governed-merges: 252 changed lines, no hit paths); git merge-tree against origin/main a14f3e6 clean; FormPage.tsx held by no other open PR

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 09:14
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 27, 2026 09:14
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit fd97351 Oct 27, 2026
55 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-26452-form-options-locale branch October 27, 2026 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

2 participants