Repository navigation
finding(i18n, a11y): 31 shipped aria-label attributes are literal English, so a non-English screen reader hears English names (the family's closing card) #12145
Description
Filing gate ①: a user-visible accessibility and localization defect with a named producer. Filed by the domain:ui seat 2 (seat post #9771, session_01TYgwmFK1q4KJ6Qq2WRLzsD). The source is the out-of-scope finding in the objectui#12124 dev report (comment 6109512642), which the contract review on PR #12142 (comment 6109713074) escalated to the seat. ⛔ Not a claim.
Read on objectui main dca25af
-
reach:named real producer.packages/components/src/renderers/complex/data-table.tsxabout:674: the row-actions trigger carriesaria-label="Row actions". That trigger is drawn on every row of a grid with row actions, so underzha screen reader announces it in English. -
The same shape recurs across shipped sources: an
aria-labelwritten as an English literal, never read through the package's translation hook. Earlier single-site cards closed one position each (for example objectui#10104, console: organization & invitation UI ships untranslated English in a zh locale (6 sites, incl. icon-only aria-labels) #4474, console i18n residual on 17.0.0 GA: built-in record-detail tabs untranslated in ja-JP / es-ES (zh-CN fine), plus two English aria-labels in every locale #4645). No open card covers the family, so this card is its closing card. -
The enumeration below is the family as of this read. Re-check with:
git grep -n -E 'aria-label="[A-Z][a-z]' -- 'packages/*/src/**/*.tsx' 'apps/*/src/**/*.tsx', dropping__tests__,.test.and.stories.paths.It finds 31 sites in 9 packages. It is a lower bound: it does not see
aria-label={'…'},title=orplaceholder=literals.
The 31 sites
apps/console/src/pages/AppDocsIndex.tsxabout:40: "Loading documentation"apps/console/src/pages/BookPage.tsxabout:57: "Loading documentation"apps/console/src/pages/DocPage.tsxabout:177: "Loading documentation"apps/console/src/pages/DocsIndex.tsxabout:34: "Loading documentation"apps/console/src/pages/DocsSlug.tsxabout:100: "Loading documentation"packages/app-shell/src/views/RecordFormPage.tsxabout:332: "Breadcrumb"packages/components/src/custom/spinner.tsxabout:17: "Loading"packages/components/src/debug/DebugPanel.tsxabout:272: "Developer Debug Panel"packages/components/src/debug/DebugPanel.tsxabout:282: "Close debug panel"packages/components/src/renderers/complex/data-table.tsxabout:674: "Row actions"packages/components/src/ui/pagination.tsxabout:75: "Go to previous page"packages/components/src/ui/pagination.tsxabout:91: "Go to next page"packages/components/src/ui/sidebar.tsxabout:321: "Toggle Sidebar"packages/fields/src/widgets/ImageCropperDialog.tsxabout:197: "Image crop preview"packages/fields/src/widgets/PercentField.tsxabout:294: "Percentage"packages/fields/src/widgets/RecordPickerDialog.tsxabout:1394: "Records"packages/plugin-chatbot/src/elements/message.tsxabout:255: "Previous branch"packages/plugin-chatbot/src/elements/message.tsxabout:279: "Next branch"packages/plugin-chatbot/src/elements/message.tsxabout:367: "Remove attachment"packages/plugin-chatbot/src/elements/message.tsxabout:395: "Remove attachment"packages/plugin-chatbot/src/elements/prompt-input.tsxabout:346: "Remove attachment"packages/plugin-chatbot/src/elements/prompt-input.tsxabout:1068: "Submit"packages/plugin-designer/src/components/HistoryPanel.tsxabout:89: "Undo"packages/plugin-designer/src/components/HistoryPanel.tsxabout:100: "Redo"packages/plugin-designer/src/components/Minimap.tsxabout:73: "Canvas minimap"packages/plugin-designer/src/components/VersionHistory.tsxabout:67: "Version history"packages/plugin-designer/src/pages/DashboardDesignPage.tsxabout:137: "Go back"packages/plugin-detail/src/renderers/record-alert.tsxabout:358: "Dismiss"packages/plugin-detail/src/renderers/record-path.tsxabout:427: "Alternative terminal stages"packages/plugin-grid/src/components/BulkActionBar.tsxabout:159: "Bulk actions"packages/plugin-list/src/components/TabBar.tsxabout:222: "Switch view"
Done when
- Each enumerated site reads its accessible name through its package's translation hook, with a key in every
@object-ui/i18npack (real translations). - Or, where a site is shown not to reach a viewer (for example
DebugPanel, if it is development-only), the PR says why it stays. - The re-check command above returns no site outside that stated list.
- Pins: per package, one role-and-name query under
zhreads thezhpack's name.
Who acts, and in what order
- The
domain:uiexecution seat, after triage grades this card. - Triage may slice it per package; the enumeration is the slicing list.
- Serial:
data-table.tsxafter PR fix(components,i18n): name the data table pager's four page buttons (objectui#12124) #12142 lands.ObjectGrid.tsxis not in this list.
Dedupe
One repo-scoped issue search on objectui, open and closed: "hard-coded English aria-label not translated accessible names locale literal strings", 31 hits. The 25 read are all closed single-site or single-surface cards (objectui#11872, #11667, #11252, #10748, #10969, #10104, #9652, #4645, #4474, #4024 and others), and none is open on these sites.
Activity
objectstack-fleet commentedon Oct 11, 2026
Triage: first grade,
bug·a11y·priority:p3·domain:ui, intopm:queue. The family's closing card, as filed, with a ratchet censusTriage seat (seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T14:00Z. ⛔ Not a claim, ⛔ not a dispatch.aria-labelliterals across 9 packages andapps/console, sodomain:ui. It is one PR: the same mechanical shape at every site, plus the pack keys.@object-ui/i18npacks. Reuse the keys PR fix(components,i18n): name the data table pager's four page buttons (objectui#12124) #12142 adds (table.*) where they fit; ⛔ no second key for the same name.packages/components/src/ui/(pagination.tsxandsidebar.tsx) are Shadcn-synced primitives, which the sync script overwrites (.claude/skills/objectui-contributor/rules/no-touch-zones.md: wrap, never edit). Give them their names through props at the wrapper or call site, not by editing the primitive.DebugPanel.tsxis developer-only. Name it in the PR body as translated or exempt, with the reason.aria-label="<English literal>"in shipped sources, tests and stories excluded. Widen it toaria-label={'…'}as well, which the filer noted the grep misses. Exceptions are named in the test.table.*keys this reuses.data-table.tsx:674("Row actions") is a11y: the data table pager's first / previous / next / last buttons have no accessible name #12124's file, so take it once fix(components,i18n): name the data table pager's four page buttons (objectui#12124) #12142 lands. This is ordering, not a block.Clause-②: no.objectstack-fleet commentedon Oct 11, 2026
Claim: PM loop round 1 (the family's closing card, as filed and as triage
6109811077directs: every enumerated site, plus the census pin)Session:
session_016djJF12Qt14ejKR5Vjt4sKAccount:
os-steveBranch:
claude/issue-12145-aria-label-i18nWorktree:
objectui-issue-12145Domain:
domain:uiSeat:
domain:ui#1File surface: the 31 sites the card enumerates, re-measured by its own re-check command on objectui
main158c75d(31 hits:apps/console5,packages/components7,plugin-chatbot6,plugin-designer5,fields3,plugin-detail2,app-shell1,plugin-grid1,plugin-list1), the lines that name them, and each package's translation-hook wiring where a site's component has none yet; forpackages/components/src/ui/pagination.tsxandsidebar.tsx, the wrapper or call site only, never the Shadcn primitive; the tenpackages/i18n/src/locales/*.tspacks (added keys only); a package's own default-translation map where it has one; one new census test; per-packagezhpins; docs pages only where they quote these names;.changeset/12145-aria-label-i18n.md(stop on breach; explain in the report)Container & model:
M(one mechanical shape at 31 sites, plus the census),mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"Clause-②: yes
Responsibility:
objectui's shipped components: 31 aria-label attributes are English literals, never read through the package's translation hook | the platform path exists: each package's translation hook and the @object-ui/i18n packs, which PR objectui#12142 just used for the data-table pager | every screen-reader user under a non-English locale; for example the data-table row-actions trigger on every grid with row actionsThread-read: 6109811077
Serial constraints cleared: none blocking. Read at 2026-10-11T14:49Z on objectui
main158c75d:table.*keys this card reuses and ownsdata-table.tsx, landed asdc6d28e.engine.home.*from them, and objectui#12110 and console: a dashboard (or list view) the server now refuses byrequiredPermissionsanswers its direct URL with 403 PERMISSION_DENIED — surface the refusal to the user (the objectui half of objectstack#22611's ruling A) #12112 may add a key. Additions here are new keys only, and the later lander mergesmain.50c7747changes none of the 31 site files. Epic objectui#7611's C9 reader list names none of them.ObjectGrid.tsx) is not dispatched. The two censuses are separate tests with no shared gate.Why
Clause-②: yes, against triage'sno: the card adds keys to every@object-ui/i18npack.packages/i18n/src/index.ts:41exportstype TranslationKeys, which istypeof en, so each added key widens the published surface. A review atCONTRACT_REVIEW_TIERis owed before the queue.objectstack-fleet commentedon Oct 11, 2026
os-dev-report
{
"issue": 12145,
"status": "done",
"branch": "claude/issue-12145-aria-label-i18n",
"pr": "#12149",
"session": "session_016djJF12Qt14ejKR5Vjt4sK (subagent: the dispatching seat's session)",
"premise_still_valid": true,
"summary": "The card's enumerated aria-label sites read their names through their own package's translation hook: 8 new keys in all ten packs (help.loadingDocumentation, common.breadcrumb, table.rowActions, fields.image.cropPreview, fields.percent.slider, designer.canvasMinimap, designer.versionHistory, grid.bulkActions), every other name reuses an existing key (table.previousPage/nextPage, common.loading, console.commandPalette.records, detail.removeAttachment, console.ai.submit/stopResponse, designer.undo/redo, detail.goBack, home.recoveryReminder.dismiss, topbar.switchView). Of the 31: 23 named (20 literals removed, 3 named at their call site: the pagination renderer for the No-Touch ui/pagination prev/next, ChatbotEnhanced for the vendored Submit), 7 exempt with reasons (DebugPanel x2 developer-only; ui/sidebar SidebarRail rendered by no objectui surface; vendored ai-elements message.tsx x4 rendered by no objectui surface), 1 not a site (record-path matched a comment quoting the objectui#5956 repair; comment reworded). The card's re-check command now returns exactly the 10 stated exempt literals. Census packages/i18n/src/tests/ariaLabelLiteralCensus-12145.test.ts (machineLocaleCensus-9909 precedent, comment-masked, four literal shapes incl. the braced widening, \p{L}): red at BASE with exactly the 20 translated sites undeclared, green at HEAD ebfc833 with 97 literal sites = 10 exempt + 87 frozen BACKLOG. Premise holds for the enumerated sites, but the enumeration is a lower bound: the card's pathspec (no :(glob) magic) never read a file directly under src/, so 87 more literal sites in 23 files / 11 packages exist; they are frozen in the census backlog (cannot grow, must leave the list when translated) and reported below for the seat to slice. H3 confirmed on the built dist: en.d.ts declares the 8 keys and TranslationKeys = typeof en is re-exported, so Clause-2 yes and @object-ui/i18n minor. H4: main did not move from 158c75d during the run; no merge needed.",
"tests": "All at HEAD ebfc833, repo-root vitest form through os-verify-lock. Census + 9 pin files: 10 files / 48 passed. packages/i18n/ + scripts/tests/check-i18n-call-site-keys.test.ts: 84 files passed (1458 tests). 130 existing test files importing or querying a changed module, two locked runs: 69 files / 1297 tests passed and 61 files / 704 tests passed. Ablation (one locked run, trap restore, ended clean at ebfc833): Leg A = the 24 site files at their BASE blobs (literal names on disk 0 to 20) with packs/tests at HEAD -> 10 files failed, 13 failed / 35 passed; census lists exactly the 20 translated sites 'declared 0, found 1'; every pin's zh case red ('Unable to find role=region and name 批量操作' etc.); restored, git diff HEAD empty. Leg B = BulkActionBar t(...) -> aria-label={'Bulk actions'} via objectstack scripts/ablation-replace.mjs (anchor 1->0, blob c9c2d24->05ef07e) -> census red (BulkActionBar.tsx · Bulk actions declared 0, found 1) + grid zh pin red; restored blob == HEAD. Leg C = ListView backlog literal removed (anchor 1->0, blob db03dae->11c9b6a) -> census red (declared 1, found 0); restored blob == HEAD. No dist in the path (vitest aliases src), so no dist preflight applies. Type-check exit 0 for i18n, components, fields, plugin-grid, plugin-list, plugin-chatbot, plugin-designer, plugin-detail, app-shell, console (script name type-check echoed for each); every new/edited test file is in its package's type-checked program.",
"mcp_calls": "0 — no MCP GitHub tool used",
"api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches), executing 3 writes as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectui/pulls (draft #12149), POST /repos//issues/12149/assignees (os-steve), POST /repos//issues/12145/comments (this report). git push not counted. Zero label writes.",
"gates": "Build: turbo run build --filter=@object-ui/console^... --concurrency=2 -> 34/34; @object-ui/console -> 35/35. Type-check: 10 packages exit 0 (list in tests). i18n: check:i18n-keys 0, check:i18n-drift 0 (8 added, 0 en changed), check:i18n-dead-keys 0 (report; no new or reused key listed), check:i18n-designer-parity 0, check:eager-locale-catalogues 0 (only en eager), check:eager-closure 0. Others exit 0: check-changeset-presence.mjs (44 source files of 10 released packages, 1 changeset), changeset:check, check:changeset-claims, check:pending-changeset-literals, check:control-bytes, check:new-line-citations (0 new), check:phantom-deps, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape; governed-queue-guard --test: NOT GOVERNED. Lint (eslint on the 34 touched ts/tsx files): 0 errors, 147 warnings, none on a changed line. Repo-wide pnpm lint and full package suites: not run locally, CI owns them. CI on #12149 at report time: in_progress (14 success, 3 skipped, 13 in progress).",
"line_budget": "45 files, +1112 / -30 = 1142 changed lines against the 3000 human-merge threshold; no split needed",
"deviations": [
"The census carries a frozen 87-site BACKLOG besides the triage's named exceptions: the census walks the tree, so it sees literal sites outside the claim's file surface; translating them would breach the surface and the slicing the PM reserved, so they are declared per file and name, counted exactly (a new site and a removed one both go red).",
"Edited beyond the 31 site lines, each as the producer or call site of a site: packages/plugin-chatbot/src/ChatbotEnhanced.tsx (its L.submit/L.stopResponse fallbacks were the English name reaching viewers; it passes the new removeLabel), packages/components/src/renderers/basic/pagination.tsx (the No-Touch primitive's call site, as triage directed), the vendored prompt-input.tsx gained an additive optional removeLabel prop (objectui#7254 precedent), the fields and designer defaults maps, and two existing tests: RecordFormPage.listLinkPlural-11733.test.tsx queried the English breadcrumb name (now reads the pack, and is app-shell's zh pin) and record-alert.test.tsx (one zh case added).",
"The apps/console pin queries by accessible name (getByLabelText), not by role: the spinner is an svg, which testing-library's role table gives no implicit role.",
"TabBarSelect's two .find(t => ...) lambdas renamed to tab so they do not shadow the new t.",
"The first commit's subject says 26 names; the true count is 23 named (20 literals removed). Pushed history is not rewritten; the PR title and body carry the right count.",
"~50 minutes of queue-timeouts (exit 99, NOT MEASURED) behind sibling holder pid 14397 (objectui-issue-12144, full plugin-detail + app-shell suites, alive and progressing) before the first acquisition; slot dev-12145 kept the place."
],
"files_changed": [
".changeset/12145-aria-label-i18n.md",
"apps/console/src/pages/AppDocsIndex.tsx",
"apps/console/src/pages/BookPage.tsx",
"apps/console/src/pages/DocPage.tsx",
"apps/console/src/pages/DocsIndex.tsx",
"apps/console/src/pages/DocsSlug.tsx",
"apps/console/src/pages/docsLoadingName-12145.test.tsx",
"packages/app-shell/src/views/RecordFormPage.tsx",
"packages/app-shell/src/views/RecordFormPage.listLinkPlural-11733.test.tsx",
"packages/components/src/custom/spinner.tsx",
"packages/components/src/renderers/basic/pagination.tsx",
"packages/components/src/renderers/complex/data-table.tsx",
"packages/components/src/tests/ariaLabelNames.zh-12145.test.tsx",
"packages/fields/src/widgets/ImageCropperDialog.tsx",
"packages/fields/src/widgets/PercentField.tsx",
"packages/fields/src/widgets/RecordPickerDialog.tsx",
"packages/fields/src/widgets/useFieldTranslation.ts",
"packages/fields/src/tests/ariaLabelNames.zh-12145.test.tsx",
"packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts (10 packs, 10 added lines each)",
"packages/i18n/src/tests/ariaLabelLiteralCensus-12145.test.ts",
"packages/plugin-chatbot/src/ChatbotEnhanced.tsx",
"packages/plugin-chatbot/src/elements/prompt-input.tsx",
"packages/plugin-chatbot/src/tests/ChatbotEnhanced.composerNames.zh-12145.test.tsx",
"packages/plugin-designer/src/components/HistoryPanel.tsx",
"packages/plugin-designer/src/components/Minimap.tsx",
"packages/plugin-designer/src/components/VersionHistory.tsx",
"packages/plugin-designer/src/hooks/useDesignerTranslation.ts",
"packages/plugin-designer/src/pages/DashboardDesignPage.tsx",
"packages/plugin-designer/src/tests/ariaLabelNames.zh-12145.test.tsx",
"packages/plugin-detail/src/renderers/record-alert.tsx",
"packages/plugin-detail/src/renderers/record-path.tsx",
"packages/plugin-detail/src/renderers/tests/record-alert.test.tsx",
"packages/plugin-grid/src/components/BulkActionBar.tsx",
"packages/plugin-grid/src/tests/BulkActionBar.regionName.zh-12145.test.tsx",
"packages/plugin-list/src/components/TabBar.tsx",
"packages/plugin-list/src/components/tests/TabBarSelect.triggerName.zh-12145.test.tsx"
],
"open_questions": [],
"out_of_scope_findings": [
"class: a (the card's own defect class; family rule: named into the closing card objectui#12145, no single-site card) · reach: named producers, shipped sources (e.g. @object-ui/plugin-list ListView's bulk-actions region, @object-ui/auth UserMenu's Profile / Settings / Sign out, @object-ui/plugin-map ObjectMap); census reading at ebfc833, not a browser measurement · evidence: 87 literal aria-label sites in 23 files that the card's re-check pathspec never read (no file directly under src/), frozen as BACKLOG in ariaLabelLiteralCensus-12145.test.ts. Proposed per-package split: plugin-designer 60 (ProcessDesigner 21, PageDesigner 18, DataModelDesigner 13, DashboardEditor 4, AppCreationWizard 3, EditorModeToggle 1), plugin-map 5, plugin-grid 4 (FormulaBar, InlineEditing), plugin-kanban 4, auth 3, layout 3, components 3 (config-panel-renderer; the No-Touch ui/breadcrumb and ui/pagination nav defaults, named only at call sites), app-shell 2 (JsonSourceEditor, QuickFind), plugin-chatbot 1 (FloatingChatbotPanel, common.closeChat exists), plugin-detail 1 (ThreadedReplies), plugin-list 1 (ListView, grid.bulkActions now exists). auth, layout and plugin-map have no @object-ui/i18n dependency · dedupe: literal aria-label backlog, untranslated accessible name, aria-label census, screen reader English",
"carrier: none (needs a ruling on how an interpolated name is keyed) · noted, not filed: 30 aria-label template literals that interpolate around English words at BASE, outside the census by design (e.g. Dismiss plus notification title in NotificationBanners / NotificationInline / NotificationSnackbar; Remove plus name in TagsField / SelectionTray; Copy plus label in two console developer pages)",
"carrier: none · noted, not filed: visible or sr-only English beside the names fixed here, code read only: the Shadcn PaginationPrevious / PaginationNext render visible Previous / Next spans; SidebarTrigger, rendered bare by the header-bar renderer, names itself through an English sr-only span",
"carrier: none · noted, not filed: ChatbotEnhanced's uploadFiles fallback is still the English literal (the console passes its own label), the same shape as the submit fallback fixed in this PR"
]
}
objectstack-fleet commentedon Oct 11, 2026
ACCEPT: PR objectui#12149 (head
ebfc833033) ·domain:uiseat 1 ·session_016djJF12Qt14ejKR5Vjt4sK· 2026-10-11T16:47ZReviewed against GitHub and the diff, not the report (
os-dev-report6111181431).Verdict
main. The first line isFixes #12145, andClause-②: yessits at a line start.os-steve.paginationrenderer for the No-Touchui/pagination.tsx,ChatbotEnhanced's fallbacks, the fields and designer defaults maps, and the two existing tests that queried the English names.packages/components/src/ui/is untouched. Nothing undercontent/docs/releases/.check-governed-merges --pr objectstack-ai/objectui#12149: NOT governed, 1,142 changed lines.6111324034, PASS onebfc8330337770d28901344d222430e6d53f9433, atCONTRACT_REVIEW_TIERby an isolated subagent.Implemented-by:the dev branch,Reviewed-by:this seat.minor: eight keys inTranslationKeys, and the optionalremoveLabelonPromptInputAttachmentProps.patch.DebugPanel×2,ui/sidebar's rail, and the vendoredmessage.tsx×4. 1 was a comment, not an attribute (record-path.tsx).console.commandPalette.records,home.recoveryReminder.dismiss,detail.goBack,topbar.switchView,table.previousPage/nextPage,common.loading.enpack ("Loading…" for the spinner; "Previous page" / "Next page" for the pagination renderer). The eight keys and the new prop are named. The control list matches the edited files.zhpin is red with the site files at BASE. Ablations B and C each went red as predicted, and every restore was blob-equal.dependabot,Test (coverage), the coverage-shard placeholder), 0 failure.Out-of-scope findings
BACKLOG: filed objectui#12150 (finding, for triage), with the per-package table as its slicing list. This answers the review's escalation ③1. finding(i18n, a11y): 31 shipped aria-label attributes are literal English, so a non-English screen reader hears English names (the family's closing card) #12145's own "Done when" is met as written, soFixes #12145stands.ChatbotEnhanced'suploadFilesfallback,SidebarTrigger's screen-reader-only span: carried on objectui#12150 as members of the same family.Previous/Nextspans in the No-Touch pagination primitive: Acceptance notes. That is visible copy, not an accessible name; its accessible name is now set at the call site.Acceptance notes
L.removeAttachmenthas nolabels.removeAttachmentarm, so a host cannot override that one name the way it cansubmitandstopResponse(review ①2). Recorded, not a breach.Landing: ready and auto-merge through the merge queue now.
objectstack-fleet commentedon Oct 11, 2026
Landed: PR objectui#12149 →
maine723f58·domain:uiseat 1 ·session_016djJF12Qt14ejKR5Vjt4sK· 2026-10-11T17:06Zf962cdc(PR objectui#12148, which merged just ahead). The card closed ascompletedthroughFixes #12145.ebfc833033:6111334826and contract review6111324034(PASS atCONTRACT_REVIEW_TIERon that head).mainnow:packages/i18n/src/__tests__/ariaLabelLiteralCensus-12145.test.tsholds 10 exempt sites and the 87-site frozen backlog. A new Englisharia-labelliteral now turns it red.pm:dispatchedis removed in this act.domain:ui,area:i18n,a11yand the grade stay.objectstack-fleet commentedon Oct 11, 2026
Correction to this seat's landing note
6111510112·domain:uiseat 1 · 2026-10-11T17:08Z: objectui#12150 is not waiting for a grade. Triage closed itnot_plannedon the maintainer's word (6111499703). The census onmainkeeps the 87 sites frozen: the count can only fall, and no seat is dispatched to drain it. Re-entry is the maintainer's direction, or a named requirement for a specific surface.