Skip to content

fix(components,plugin-detail,i18n): 把剩余的控制台 chrome 无障碍名接入 i18n (objectstack#5430) - #3390

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-5430-chrome-a11y-names-i18n
Aug 5, 2026
Merged

fix(components,plugin-detail,i18n): 把剩余的控制台 chrome 无障碍名接入 i18n (objectstack#5430)#3390
yinlianghui merged 2 commits into
mainfrom
claude/issue-5430-chrome-a11y-names-i18n

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixesobjectstack-ai/objectstack#5430

背景

四处 icon-only 控件的可访问名仍是硬编码英文字面量。因为这些控件没有可见标签,这个字面量对屏幕阅读器和 hover tooltip 而言就是控件本身 —— 在 zh/ja/es 会话下,它们是记录页 chrome 里仅剩的英文。

已对 origin/main 逐条核实四处仍然存在(#5407/#3341 的同包改动已合入,行号有漂移,以下为当前 main 的行号):

字面量位置
Page header actionspackages/components/src/renderers/layout/containers.tsx:1201 —— role="toolbar" 的可访问名,就在 #5407 修掉的 More actions 触发器上方 8 行
Emoji pickerpackages/plugin-detail/src/ReactionPicker.tsx:88 —— role="listbox" 弹层
{emoji} {count} reaction(s)同文件 :64 —— 每个表情 chip,英文复数由字符串拼接内建
Close + Close panelpackages/components/src/custom/navigation-overlay.tsx:391-392 与 :476

改动

站点新词条
page:header 的 toolbardetail.pageHeaderActions
ReactionPicker 弹层detail.emojiPicker
ReactionPicker 表情 chipdetail.reactionCount / detail.reactionCountOne
NavigationOverlay 抽屉关闭common.close(复用控制台其余部分已在用的键,不新造)
NavigationOverlay 分栏面板关闭common.closePanel

五个新词条十个语言包全补(all-locales-key-parity 强制)。

复数项:双 key,不用 _one/_other

按正文口径走本仓既有的双 key 约定(detail.relatedRecords/relatedRecordOnelookup.recordCount/recordCountOne),⛔ 不引入 i18next 的 _one/_other 后缀 —— zh/ja/ko 没有独立单数形式,那些包会合理地_one 那一半,parity 门禁会把它读成丢键。调用点按 count === 1 显式二选一。

navigation-overlay.tsx 原本完全没接 i18n,新增了 useSafeTranslate,放在所有条件 return 之上(rules-of-hooks)。

测试

新增三个测试文件,每处en 正向 + 至少一个非 en 正向 + 英文字面量反向断言;复数项两个 key 各一例(count=3 走复数键,count=1 走 …One 键),并特意断言 zh 下两个 key 同形 —— 这正是后缀方案不适用本仓的原因。

Test Files 3 passed (3)
Tests 15 passed (15)

反向验证(方向为先验预测,非事后解释)

预测:把四处调用点改回英文字面量后,非 en 断言应全红,en 断言应全绿(字面量恰好等于 en 包的值)。实测与预测一致:

Tests 10 failed | 5 passed (15)

10 条失败全部是 zh/ja/es/fr/de 用例,5 条通过全部是 en 用例 —— 四处站点都被真实钉住,没有空断言。

回归面

packages/components 78 files / 568 tests passed
packages/i18n + plugin-detail + plugin-view 74 files / 711 tests passed
type-check (components, plugin-detail, i18n) Done
lint 0 errors
check:control-bytes OK (3581 files)
changeset:check OK(未声明 major)

消费半径已按规则外扫(不只按改动包):packages/plugin-view/src/__tests__/ObjectView.test.tsxgetByLabelText('Close panel') 渲染的正是本组件的分栏模式,packages/components/src/__tests__/page-header-actions.test.tsx:197 用正则匹配 toolbar 名 —— 两者无 provider 时走英文 fallback,均仍绿,已实跑确认。

一处值得复核的测试写法

抽屉关闭按钮title 定位,而不是 role+name。shadcn Sheet 原语自己也会渲染一个关闭按钮,其可访问名是硬编码英文 sr-only 文本(packages/components/src/ui/sheet.tsx:80,No-Touch 区)。NavigationOverlay[&>button:last-of-type]:hidden 把它 CSS 隐藏,真实浏览器里它不在无障碍树上;但 jsdom 不套用 Tailwind,RTL 仍能看到它,于是 en 下 getByRole('button', { name: 'Close' }) 会匹配到两个元素。我们自己的按钮是唯一带 title 的,按 title 定位才精确。原语那个未翻译的标签是独立的越界发现,已单独归档,本 PR 不碰。

越界发现(均已按 Prime Directive #10 归档为 unassigned issue,本 PR 不修)


Generated by Claude Code

…tstack#5430)
四处 icon-only 控件的可访问名仍是硬编码英文字面量;因为控件没有可见标签,
这个字面量对屏幕阅读器和 hover tooltip 而言就是控件本身。
- page:header 的 role="toolbar" → detail.pageHeaderActions
- ReactionPicker 的 role="listbox" 弹层 → detail.emojiPicker
- ReactionPicker 每个表情 chip(原本用字符串拼接内建英文复数) →
detail.reactionCount / detail.reactionCountOne
- NavigationOverlay 抽屉关闭 / 分栏面板关闭 → common.close / common.closePanel
复数项走本仓既有的双 key 约定(relatedRecords/relatedRecordOne、
recordCount/recordCountOne),不引入 i18next 的 _one/_other 后缀:
zh/ja/ko 没有独立单数形式,那两个包会合理地缺 _one,
all-locales-key-parity 会把它读成丢键。
五个新词条十个语言包全补。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
按 role+name 定位会同时命中 shadcn Sheet 原语自动渲染的关闭按钮
(packages/components/src/ui/sheet.tsx:80 的 sr-only "Close",属 No-Touch 区)。
NavigationOverlay 用 [&>button:last-of-type]:hidden 把它 CSS 隐藏,
真实浏览器里它不在无障碍树上;但 jsdom 不套用 Tailwind,RTL 仍能看到它,
于是 en 会话下 getByRole('button', { name: 'Close' }) 匹配到两个元素。
我们自己的关闭按钮是唯一带 title 的,按 title 定位才精确。
原语自身未翻译的标签是另一条越界发现,单独归档。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercelBot commented Aug 5, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 5, 2026 2:33pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)28.1 KB350 KB
Entry fileindex-BhBRxQtx.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)477.42KB104.79KB
core (index.js)2.25KB0.80KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)136.23KB34.75KB
fields (index.js)227.18KB55.72KB
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.01KB28.86KB
plugin-designer (index.js)210.51KB42.51KB
plugin-detail (index.js)231.54KB57.09KB
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.55KB10.59KB
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 5, 2026 14:37
@yinlianghui
yinlianghui added this pull request to the merge queueAug 5, 2026
Merged via the queue into main with commit b71fc92Aug 5, 2026
17 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-5430-chrome-a11y-names-i18n branch August 5, 2026 14:37
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@yinlianghui@claude