fix(i18n): ListView / ObjectGrid 记录详情浮层标题改为入键,不再自拼英文 (#3426) - #3457
Merged
Conversation
…3426) PR #3423 gave NavigationOverlay's `resolvedTitle` an i18n default (`detail.recordDetail`), but two hosts never let it run: they string-built an English heading in TypeScript and passed it as the `title` prop, so a zh/ja/de session got a fully localized drawer with one English heading. Both paths are user-reachable: `list-view` / `object-grid` are public page blocks and `navigation` is an authorable key on their schema, so authoring `navigation: { mode: 'drawer' }` opens exactly this overlay on row click. app-shell's ObjectView suppresses it (own `onRowClick` + own overlay), but that is one host overriding a public block, not a dead branch. - new `detail.recordDetailWithLabel` (`{{label}} Detail`) in all ten packs, so packs whose qualifier trails the noun (de) or that need a possessive particle (ja/zh) write their own arrangement instead of English word order - the no-label branch reuses `detail.recordDetail`, the key the overlay itself defaults to, rather than minting a twin - both keys added to LIST_DEFAULT_TRANSLATIONS / GRID_DEFAULT_TRANSLATIONS so a provider-less host still reads English, byte-identical to before Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 6, 2026 06:45
This was referenced Aug 6, 2026
akarma-synetal
pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Aug 6, 2026
…tstack-ai#3459) (objectstack-ai#3464) * fix(i18n): key the kanban / tree / view record-detail headings (objectstack-ai#3459) objectstack-ai#3426 (PR objectstack-ai#3457) keyed ListView and ObjectGrid; the same grep found three more hosts string-building an English heading in TypeScript, so the surrounding drawer/panel was fully localized with one English phrase on it. All three were re-verified against origin/main before implementing, each with a test that drives the real interaction rather than by inspection: - ObjectKanban — reachable, and needs no authoring at all: navConfig DEFAULTS to `{ mode: 'drawer' }` and every card's onCardClick is wired to navigation.handleClick. Two premise corrections: the consumer is RecordDetailDrawer, not NavigationOverlay, and its title renders as an sr-only SheetTitle — the drawer's accessible name, not a visible heading. - ObjectTree — reachable, but only with `navigation` authored explicitly (no default) and only standalone; ListView passes its own onRowClick and neither host forwards `navigation`. - ObjectView (mode 'split') — reachable; `navigation` is a declared authorable input and `layout` cannot reach the branch (ObjectViewSchema.layout excludes 'split', deriveRecordSurface never returns it). app-shell pins layout 'page', a host override. Zero new locale keys: `detail.recordDetail` and `detail.recordDetailWithLabel` already ship in all ten packs from objectstack-ai#3457, and reusing them keeps one heading on one control. Each plugin gains its own English defaults map for the provider-less path; plugin-tree gains a dependency on @object-ui/i18n for it. Visible English change, one, deliberate: the tree overlay goes from the plural `Record Details` to the singular `Record Detail` (maintainer ruling on objectstack-ai#3459 — normalize the strays, do not mint a plural key). A repo-wide grep found no e2e spec and no unit test addressing the old string. Every other branch is byte-identical with and without a provider. The kanban's other plural (`'Card Details'`) is NOT a visible change: that branch fires only when schema.objectName is falsy, while the drawer consuming it returns null on the same condition. Keyed anyway so the literal cannot leak if the guard relaxes; deliberately untested, with the deadness itself pinned instead. Reverse-verified by restoring all three files to origin/main: 11 of 20 assertions go red (kanban zh/ja/de, view zh/ja/de, tree en+zh/ja/de, tree no-provider en) and 9 stay green (every byte-identical English branch). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt * test(kanban): preload the lazy KanbanImpl chunk at module scope (objectstack-ai#3459) `KanbanRenderer` renders `React.lazy(() => import('./KanbanImpl'))` behind a Suspense boundary, and every assertion in the two new overlay-heading files sits AFTER that boundary — a card must be on screen before it can be clicked. That is exactly the flake class AGENTS.md §测试纪律 describes: an unbounded module load charged to a bounded `findBy` window (a first `import()` measured at ~976ms against RTL's 1000ms default under full CI parallelism). Moving the import to module scope pays the cost in the import phase, which no test/hook timeout constrains. The specifier is byte-identical to `./index`'s so ESM resolves both to one cache entry and the component's own lazy factory settles immediately — not `beforeAll`, which is bound by the NARROWER 10s hookTimeout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3426
PR #3423 给
NavigationOverlay的resolvedTitle接上了 i18n 默认值(detail.recordDetail),但两个调用方在传入侧自拼英文标题、直接塞进titleprop —— 默认值根本轮不到生效。结果是:一个 zh/ja/de 会话拿到一个通体本地化的抽屉,顶上却挂着唯一一句英文标题。packages/plugin-list/src/ListView.tsx——`${schema.label} Detail`packages/plugin-grid/src/ObjectGrid.tsx—— 同样的模板串,外加无 label 时的裸字面量'Record Detail'前提核验:这两条路径确实用户可达
Issue 明确把可达性留给实现者判定,这是核验结论(证据均在
origin/main):list-view/object-grid是公开页面块(packages/core/src/registry/public-blocks.ts),navigation是它们 schema 上的可编写键(ViewNavigationConfig,对齐@objectstack/spec ListView.navigation)。useNavigationOverlay把mode: 'drawer' | 'modal' | 'split' | 'popover'翻成isOverlay;两个插件都把navigation.handleClick接到行点击上,并各自渲染NavigationOverlay。navigation: { mode: 'drawer' },点行就会开出这个浮层 —— 不经过 console/app-shell。唯一会压制它的宿主是 app-shell 的
ObjectView:它传自己的onRowClick(在useNavigationOverlay里具有最高优先级,直接 return),并渲染自己的NavigationOverlay。那是一个宿主对公开块的覆盖,不能反推分支是死的。新增的四个测试文件就是这条可达性的可执行证据:它们完全按用户路径打开浮层(点行 → 断言标题),没有任何对内部状态的直接操作。
改法(调用方入键,不给消费方加宽容)
两处都改成从 key 取标题,而不是拼字符串:
detail.recordDetailWithLabel(en:'{{label}} Detail')用插值携带对象标签,这样限定词后置的语种(de 用连字符复合词)或需要领属助词的语种(ja/zh)可以自己排语序,而不是被英文语序焊死;detail.recordDetail—— 正是浮层自身的默认键 —— 而不是另铸一个孪生键,避免同一个控件上的同一句话分裂成两份会各自漂移的译文。新键补进全部十个语言包,并且同时补进两个插件各自的英文兜底表(
LIST_DEFAULT_TRANSLATIONS/GRID_DEFAULT_TRANSLATIONS)—— 那才是没有挂I18nProvider时createSafeTranslation真正读的东西。英文输出逐字节不变
三个分支都不变:
Contacts Detail/Contacts Detail/Record Detail,挂 provider 与不挂 provider 皆然(兜底路径里createSafeTranslation自己会替换{{label}})。既有 e2e 用英文名寻址这块 chrome 的写法照旧可用。验证方向(先预判,再运行)
反向核验实测(用
git checkout origin/main --取回原始源文件后重跑,再还原):两次唯一活下来的绿色用例,都正是
en那条 —— 与预判完全一致。正向全量(仓根,
flock+--maxWorkers=2):all-locales-key-parity(含 placeholder 形状比对)覆盖了新键,已在上面这 987 条里绿。type-check / lint:
远端 CI 已全绿(17 项 success / skipped,含 Test 四个分片、Type Check、Lint、Control Byte Scan、Changeset Bump Policy 与 Fixed Group Check)。
关于「为什么兜底测试要单独一个文件」
createI18n会instance.use(initReactI18next),而initReactI18next把该实例注册成 react-i18next 的模块级全局默认,这个注册在 unmount 和cleanup()之后依然存活。所以只要同一文件里有任何一个用例挂过I18nProvider(哪怕defaultLanguage是 zh 且早已卸载),后面所有「不挂 provider」的渲染都会静悄悄地落到那个中文实例上 —— 看起来是绿的,其实什么都没断言。沿用 plugin-detail 在 objectstack#5733 立下的同一处理(两个文件、文件头写清原因)。越界未改(已记录为 out-of-scope)
同一类缺陷在本 PR 文件围栏之外还有三处,均未在此 PR 触碰,已单独立为 #3459(未指派、未打标,待 PM triage):
plugin-kanban/src/ObjectKanban.tsx(${objectName} Detail加'Card Details')、plugin-tree/src/ObjectTree.tsx(title直接给字面量"Record Details")、plugin-view/src/ObjectView.tsx(${objectLabel} Detail)。