#3426 (PR #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 #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 #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
Fixes #3459
#3426(PR #3457)修了 ListView / ObjectGrid;同一把 grep 找到的另外三处宿主同样在 TypeScript 里拼英文标题,于是 zh/ja/de 会话拿到一个通体本地化的浮层、顶上挂一句英文。
一、先证伪:三处的可达性逐个重判(#3426 标准)
立单者未核验,派发要求先做判定。三处都在 origin/main 上跑了真实交互测试(渲染块 → 点卡片/行 → 读标题),不是看代码推断。结论两处如立单所述,一处的前提要修正:
ObjectKanbannavConfig在 schema 不写navigation时默认{ mode: 'drawer' },每张卡的onCardClick直连navigation.handleClick—— 页面里放个object-kanban(公开页面块)就能开出这个抽屉ObjectTreenavigation: { mode: 'drawer' }(无默认),且只在独立渲染时;ListView传自己的onRowClick,两个宿主都不向下转发navigation。object-tree也不在PUBLIC_BLOCKS里 —— 是渲染能力而非编写词表ObjectView(mode: 'split')navigation是该注册项声明过的可编写输入({ name: 'navigation', type: 'object' }),handleRowClick在mode === 'split'下置isFormOpen。layout到不了:ObjectViewSchema.layout是 `'drawer'app-shell 的 ObjectView 包装器钉
layout: 'page'并给onNavigate,ListView 传自己的onRowClick—— 都是宿主覆盖已注册的块,不是分支死掉(与 #3457 的判词一致)。立单前提的两处修正(kanban)
NavigationOverlay,是RecordDetailDrawer(来自@object-ui/plugin-detail)。所以「resolvedTitle = title || t('detail.recordDetail')默认值永不生效」这条论据对 kanban 不成立 —— 那里压根不经过NavigationOverlay。RecordDetailDrawer把title渲染进SheetHeader className="sr-only"的SheetTitle,可见标题由 DetailView 自己的 HeaderHighlight 画。所以这串字是浮层对读屏器的可访问名。缺陷依然成立(zh 会话下读屏器只被告知一句英文),而且同一个文件早就因为同样的理由把 resize handle 的aria-label接进了语言包(objectstack#5733);但断言写的是toHaveAccessibleName,不是「找一个可见标题」,因为代码渲染的就是前者。'Card Details'那条分支是死的'Card Details'只在!schema.objectName时产生,而消费它的 IIFE 在同一条件下return null。实测:不带objectName渲染看板、点卡片,queryByRole('dialog')为null,浮层根本不开。处置:照样 key 化,但刻意不给它写断言 —— 那样的断言会「因为什么都没渲染」而通过,不是因为逻辑对。改为把「无 objectName 时压根不开浮层」这个事实本身钉成测试。死分支本身另立观察单 #3463。
二、改动
三处都改走既有的
detail.*两个 key:有标签走detail.recordDetailWithLabel({{label}} Detail,让各语言包自己决定语序 —— de 用连字符复合、ja/zh 需要助词),无标签复用detail.recordDetail(NavigationOverlay自己兜底的那个 key)。新增语言包 key:零个。 两个 key 在 #3457 已进十包,复用它们才能保证一个控件一句翻译、不各自漂移。
每个插件各自建英文兜底表(
KANBAN_DEFAULT_TRANSLATIONS/TREE_DEFAULT_TRANSLATIONS/VIEW_DEFAULT_TRANSLATIONS),给无I18nProvider的宿主用;@object-ui/plugin-tree为此新增了对@object-ui/i18n的依赖。kanban 没有复用文件里已有的
tt(useSafeTranslate):它的签名是tt(key, fallback),没有 options 参数,{{label}}会原样进 DOM。三、可见英文变化(明确列出)
一处,刻意的:tree 浮层标题
Record Details→Record Detail。按维护者对 #3459 的裁定(Option A:复数拼写归一到单数detail.recordDetail家族,一个控件一句翻译,不为复数另开 key)。这也是NavigationOverlay自己兜底、以及整个detail.*家族已经在用的拼写。依赖前先 grep 过:全仓
Record Details/Card Details三处命中,e2e/零命中,单元测试零命中;第三处是plugin-detail/src/index.tsx的注册面板 label,与浮层标题无关,未动。kanban 的
Card Details不计入可见变化 —— 见上,那条分支不可达,用户看不到任何差别。其余每个分支英文逐字节不变(
Contacts Detail/Support cases Detail/Contacts Detail),有无 provider 都一样。四、验证(仓根跑,方向先预测后执行)
反向验证:三份源文件
git checkout origin/main --回退后重跑预测与实测完全一致,20 条断言 11 红 9 绿:
Expected accessible name: Contacts详情 / Received: Contacts DetailUnable to find an element with the text: 联系人详情Unable to find an element with the text: Record Detail注意 tree 的两条 en 断言是本 PR 唯一「改前绿改后也该绿」不成立的地方,而这是裁定的结果不是回归 —— 模板预设的「en 逐字节不变」在这一处不适用,如实标出。
无 provider 的英文兜底按要求每个包各自独立成文件(
initReactI18next把实例注册成模块全局,活过cleanup(),与 provider 用例同文件会静默解析到上一个语言)。五、围栏
未越界。未动
packages/components(NavigationOverlay一行没改)、content/docs/releases/、packages/spec、packages/i18n/src/locales/*(零新 key)。改动清单:三个插件源文件 + 六个测试文件 +
plugin-tree的package.json/vite.config.ts(新依赖)+pnpm-lock.yaml(仅该依赖一行)+ 一个 changeset(patch × 3,fixed 组约定,未声明 major)。六、顺手发现,已另立单(未在本 PR 修)
Create/Edit/View ${label},喂给 Drawer/Dialog 标题与 popover 浮层 —— #3426/#3459 的同族漏网 #3462(具体缺陷,未打标签待 PM 定级):同文件的getFormTitle()自拼Create/Edit/View ${objectLabel},喂给DrawerTitle/DialogTitle/ popover 模式的NavigationOverlay.title。已用同标准核验可达:zh 会话 +navigation: { mode: 'drawer' },抽屉标题实测为View 联系人。门槛比 split 那处还低(layout默认就是'drawer')。属于另一组字符串(动词 + 标签),不在 [i18n] 另外三个插件也自拼英文标题传给 NavigationOverlay 的 title prop(kanban / tree / plugin-view),#3426 的围栏没覆盖到 #3459 围栏内。navIsOverlay算了从不用,detailTitle的无 objectName 分支不可达 #3463(finding,不进队列):ObjectKanban的navIsOverlay算了从不用;以及上面那条detailTitle死分支的结构本身。Generated by Claude Code