Skip to content

fix(plugin-detail): 把记录详情抽屉拖拽手柄的硬编码英文接入 i18n (objectstack#5733) - #3429

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-5733-detaildrawer-resize-i18n
Aug 6, 2026
Merged

fix(plugin-detail): 把记录详情抽屉拖拽手柄的硬编码英文接入 i18n (objectstack#5733)#3429
yinlianghui merged 1 commit into
mainfrom
claude/issue-5733-detaildrawer-resize-i18n

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixesobjectstack-ai/objectstack#5733

问题

packages/plugin-detail/src/RecordDetailDrawer.tsx:277 带着一个 aria-label="Resize drawer" 硬编码字面量,与 objectstack#5506(objectui #3423)刚从 packages/components/src/custom/navigation-overlay.tsx 移除的那个逐字节相同。#5506 的改动范围只圈了 packages/components,packages/plugin-detail 不在其中,这份副本因此漏网。

两处是同一个控件形态:抽屉左边缘的拖拽调宽手柄,role="separator" + aria-orientation="vertical",没有任何可见标签——所以这个字符串对读屏软件而言"就是"这个控件本身。结果是 zh / ja / de 会话在一个整体已本地化的抽屉里,只有这一处念英文。

也不是死代码:resizable 默认为 true,plugin-kanban 的 ObjectKanban、plugin-calendar 的 ObjectCalendar 点开的就是它(plugin-gantt 在测试里把它 mock 掉)。

前提核验(基于 origin/main@5dd012776)

Issue 只是线索,三条前提逐条在 origin/main 上验过:

顺带确认探针 key detail.back 在 en/zh/de/ja 包里都在——挂了 provider 时 createSafeTranslation 才会走真 t,common.resizeDrawer 才解析得到语言包的值。

改动

两行实质改动:

  1. RecordDetailDrawer.tsx —— 字面量换成 t('common.resizeDrawer')
  2. useDetailTranslation.ts —— 往 DETAIL_DEFAULT_TRANSLATIONS 加一条 'common.resizeDrawer': 'Resize drawer'

刻意复用 #5506 的同一个 key,而不是新造 detail.resizeDrawer 孪生 key。 这是本 PR 唯一一处有取舍的地方:它让 DETAIL_DEFAULT_TRANSLATIONS 出现了第一个 common.* 条目(其余全是 detail.*)。但同一个控件从两个包渲染,不该拿到两份会各自漂移的翻译;新造 key 还要再动十个语言包(而 packages/i18n 本来就不该由这个 PR 碰)。代码里留了注释说明这一条。

那条 defaults 是无 provider 时的回退表——缺了它,所有没挂 I18nProvider 的宿主会退化成显示原始 key common.resizeDrawer

测试(两个文件,方向先预测后运行)

这不是一个"全红转全绿"的改动,三个方向分开说清楚:

用例改前改后
zh / de / ja 会话下手柄读语言包的值REDGREEN
en 会话下仍读 Resize drawerGREENGREEN
无 provider 时回退英文GREENGREEN

后两行是如实记录:它们钉的是"回退没被改坏",不是这次修复本身。en 一行证明把名字接进 t() 没有改变英文会话听到的内容;无 provider 一行钉的是 defaults 表——少了那条会渲染出原始 key 而变红,那正是它存在的理由。

反向验证真跑了:把两处源码改动 stash 掉、只留新测试,得到

× reads the zh bundle value under a zh session
→ expected 'Resize drawer' to be '调整面板宽度'
× reads the de bundle value under a de session
→ expected 'Resize drawer' to be 'Panelbreite anpassen'
× reads the ja bundle value under a ja session
→ expected 'Resize drawer' to be 'パネル幅を調整'
Test Files 1 failed | 1 passed (2)
Tests 3 failed | 3 passed (6)

与预测完全一致。

为什么是两个文件而不是一个 describe:createI18n 会调 instance.use(initReactI18next),把该实例注册成 react-i18next 的模块级全局默认,这个注册在 unmount 和 cleanup() 之后依然存在。所以只要文件里有任何一个用例挂过 provider,同文件里之后所有"无 provider"渲染都会静默地解析到那个实例上——要么绿得毫无意义,要么红得莫名其妙。无 provider 的断言因此单独成文件,且该文件不 import provider;vitest 的 dom project 是 isolate: true,能保证它拿到干净的全局。这一条在文件头注释里写死了。

两个文件都把 DetailView / InlineEditSaveBar mock 掉(沿用同目录 RecordDetailDrawer.capability.test.tsx 的做法),这样树里唯一的 role="separator" 就是被测手柄。另外补了一个 resizable: false 的用例——否则上面几条 locale 断言在"手柄整个不渲染"的情况下也可能因为查不到东西而误绿。

消费半径排查

按规则的调用方而不是按改动的包来扫:grep 全仓 Resize drawer / resizeDrawer / role="separator" 断言,RecordDetailDrawer 的三个宿主包(kanban / calendar / gantt)的 fixture 与 spec 里,没有任何一处按英文名寻址这个抽屉的手柄。packages/components 下命中的三处都是 NavigationOverlay 自己的测试,是另一个组件。

验证输出

# 全量 plugin-detail 套件(含两个新文件)
Test Files 52 passed (52)
Tests 450 passed (450)
# 两个新文件确实跑到了(verbose 里核对过文件名)
✓ .../RecordDetailDrawer.resizeHandleI18n.test.tsx (5 个用例)
✓ .../RecordDetailDrawer.resizeHandleNoProviderFallback.test.tsx (1 个用例)
# type-check(先 build 了依赖图,否则新 worktree 里 tsc 解析不到 @object-ui/* 声明)
pnpm --workspace-concurrency=2 --filter @object-ui/plugin-detail type-check → 通过
# 门禁
node scripts/check-control-bytes.mjs ✅ OK (3622 tracked text files)
node scripts/check-changeset-no-major.mjs ✅
node scripts/check-changeset-fixed.mjs ✅
eslint(四个改动文件) 0 errors(8 个 warning 全在未触碰的既有行上)

另按字节纪律对五个改动文件做了门禁之外的自查(grep -naP 扫控制字符),无命中。

changeset 按 #3423 的先例补了一个(patch,@object-ui/plugin-detail),因为这是十个 locale 下用户可见的可访问名变化。


Generated by Claude Code

RecordDetailDrawer 左边缘的拖拽调宽手柄带着一个 aria-label="Resize drawer"
硬编码字面量,与 objectstack#5506 刚从 NavigationOverlay 移除的那个逐字节相同
——#5506 的改动范围只圈了 packages/components,这份副本因此漏网。
该手柄没有任何可见标签,所以这个字符串对读屏软件而言"就是"这个控件本身:
zh/ja/de 会话在一个整体已本地化的抽屉里只有这一处英文。它也不是死代码,
resizable 默认为 true,plugin-kanban / plugin-calendar / plugin-gantt 点开的
就是它。
现改为 t('common.resizeDrawer'),刻意复用 #5506 给另一个手柄的同一个 key
(十个语言包里都已存在),而不是新造一个 detail.resizeDrawer 孪生 key ——
同一个控件从两个包渲染,不应该拿到两份会各自漂移的翻译。
common.resizeDrawer 同时加入 DETAIL_DEFAULT_TRANSLATIONS,即
createSafeTranslation 在没有 I18nProvider 时回退的那张表;缺了这一条,
所有无 provider 的宿主都会退化成显示原始 key。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercelBot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 6, 2026 3:23am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)28.1 KB350 KB
Entry fileindex-Dbd1MLPg.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.47KB3.09KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)7.57KB2.97KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)22.10KB4.37KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.25KB0.53KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)478.80KB105.18KB
core (index.js)2.47KB0.91KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)136.23KB34.75KB
fields (index.js)229.34KB56.32KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.65KB1.06KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)9.48KB3.27KB
i18n (useObjectLabel.js)26.14KB6.07KB
i18n (useSafeTranslation.js)3.26KB1.44KB
layout (index.js)38.53KB10.71KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.05KB1.53KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)44.98KB12.37KB
plugin-charts (index.js)61.04KB17.31KB
plugin-chatbot (index.js)180.09KB42.72KB
plugin-dashboard (index.js)112.03KB28.88KB
plugin-designer (index.js)210.51KB42.51KB
plugin-detail (index.js)232.53KB57.37KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)111.54KB26.97KB
plugin-gantt (index.js)162.55KB39.57KB
plugin-grid (index.js)185.08KB49.04KB
plugin-kanban (index.js)47.89KB13.18KB
plugin-list (index.js)105.02KB25.36KB
plugin-map (index.js)16.81KB5.24KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.58KB10.58KB
plugin-timeline (index.js)25.76KB7.33KB
plugin-tree (index.js)8.34KB2.82KB
plugin-view (index.js)83.67KB20.43KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)19.28KB6.38KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.02KB0.55KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)2.46KB1.21KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)0.20KB0.18KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@yinlianghui
yinlianghui marked this pull request as ready for review August 6, 2026 03:29
@yinlianghui
yinlianghui added this pull request to the merge queueAug 6, 2026
Merged via the queue into main with commit 5a24ad9Aug 6, 2026
17 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-5733-detaildrawer-resize-i18n branch August 6, 2026 03:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

objectui: a second hardcoded aria-label of "Resize drawer" in plugin-detail's RecordDetailDrawer — same literal #5506 localized in NavigationOverlay

2 participants

@yinlianghui@claude