This repository was archived by the owner on Oct 26, 2026. It is now read-only.
Repository navigation
Commit e723f58
fix(i18n,a11y): the enumerated literal aria-label names read each package's translation hook, and a census pins the family (objectui#12145) (#12149)
Fixes #12145
Clause-②: yes
The card's enumerated `aria-label` sites read their accessible names
through their own package's translation hook, so a screen reader under
`zh` (or any non-English pack) hears the viewer's language. Eight keys
are added to all ten `@object-ui/i18n` packs with real translations;
every other name reuses a key that already holds the same words. The
family is pinned by a census test in the ordinary suite (not a CI gate),
beside per-package `zh` role-and-name pins.
`Clause-②: yes` is the claim's reading (comment 6110243674): the added
keys widen the `as const` `en` pack, and so the published
`TranslationKeys`. Confirmed on the built declarations at HEAD
`ebfc833`: `packages/i18n/dist/locales/en.d.ts` declares all eight keys,
and `dist/index.d.ts` re-exports `TranslationKeys` (`typeof en`).
## Every enumerated site, and what happened to it
| Site (the card's 31) | Disposition | Key |
|---|---|---|
| `apps/console` `AppDocsIndex`, `BookPage`, `DocPage`, `DocsIndex`,
`DocsSlug`: "Loading documentation" (5) | read through
`useObjectTranslation` | `help.loadingDocumentation` (new) |
| `app-shell` `RecordFormPage`: "Breadcrumb" | read through the page's
`t` | `common.breadcrumb` (new) |
| `components` `custom/spinner.tsx`: "Loading" | read through a
`createSafeTranslation` hook; a caller's `aria-label` still wins |
`common.loading` (reused; the English name is now "Loading…") |
| `components` `debug/DebugPanel.tsx`: "Developer Debug Panel", "Close
debug panel" | **exempt**: a developer tool, not product UI. No objectui
surface mounts it (a host opts in behind `?__debug` / Ctrl+Shift+D), and
its whole chrome is English developer copy, so translating two names
would leave it half in one language | none |
| `components` `renderers/complex/data-table.tsx`: "Row actions" | read
through `useTableTranslation` | `table.rowActions` (new) |
| `components` `ui/pagination.tsx`: "Go to previous page", "Go to next
page" | the No-Touch primitive is **not edited**; its one shipped call
site, the `pagination` renderer (`renderers/basic/pagination.tsx`),
passes the names, which the primitive spreads after its defaults |
`table.previousPage` / `table.nextPage` (reused, PR objectui#12142) |
| `components` `ui/sidebar.tsx`: "Toggle Sidebar" (`SidebarRail`) |
**exempt**: the No-Touch primitive is not edited, and no objectui
surface renders the rail. Its only writer is the CLI's app generator,
whose generated app mounts no `I18nProvider`. A host names it through
`aria-label` / `title` | (`common.toggleSidebar` for a host) |
| `fields` `ImageCropperDialog`: "Image crop preview" |
`useFieldTranslation` | `fields.image.cropPreview` (new) |
| `fields` `PercentField`: "Percentage" | `useFieldTranslation` |
`fields.percent.slider` (new) |
| `fields` `RecordPickerDialog`: "Records" | `useFieldTranslation` |
`console.commandPalette.records` (reused) |
| `plugin-chatbot` `elements/message.tsx`: "Previous branch", "Next
branch", "Remove attachment" x2 | **exempt**: vendored from
vercel/ai-elements (the header says wrap, do not edit), and no objectui
surface renders `MessageBranchPrevious`, `MessageBranchNext` or
`MessageAttachment`; they are only re-exported as `AIElements`. The
branch buttons spread `props` last | none |
| `plugin-chatbot` `elements/prompt-input.tsx`: "Remove attachment"
(`PromptInputAttachment`) | rendered by `ChatbotEnhanced`, and `props`
reach the chip, not the button. An optional `removeLabel` prop is added
to the vendored element (the objectui#7254 `statusLabels` precedent:
additive, the upstream English stays when omitted); `ChatbotEnhanced`
passes the pack's name | `detail.removeAttachment` (reused) |
| `plugin-chatbot` `elements/prompt-input.tsx`: "Submit"
(`PromptInputSubmit`) | the vendored default stays (**exempt**); its one
call site, `ChatbotEnhanced`, already overrode it with `L.submit`, whose
fallback WAS the English literal. That producer now reads the pack, for
the stop state too | `console.ai.submit` / `console.ai.stopResponse`
(reused: the console already passes them) |
| `plugin-designer` `HistoryPanel`: "Undo", "Redo" |
`useDesignerTranslation` | `designer.undo` / `designer.redo` (reused) |
| `plugin-designer` `Minimap`: "Canvas minimap" |
`useDesignerTranslation` | `designer.canvasMinimap` (new) |
| `plugin-designer` `VersionHistory`: "Version history" |
`useDesignerTranslation` | `designer.versionHistory` (new) |
| `plugin-designer` `DashboardDesignPage`: "Go back" |
`useDesignerTranslation` | `detail.goBack` (reused) |
| `plugin-detail` `record-alert.tsx`: "Dismiss" | the renderer's `t` |
`home.recoveryReminder.dismiss` (reused) |
| `plugin-detail` `record-path.tsx`: "Alternative terminal stages" |
**not a site**: the card's grep matched a comment that quotes the
objectui#5956 repair. The comment is reworded so the card's command no
longer matches it; no attribute existed | none |
| `plugin-grid` `BulkActionBar`: "Bulk actions" | the bar's `t` |
`grid.bulkActions` (new) |
| `plugin-list` `TabBar` (`TabBarSelect`): "Switch view" |
`useObjectTranslation` | `topbar.switchView` (reused: the app header's
view switcher reads it) |
"Reused" follows triage's rule, one key per name: where the English name
already had a key, the site borrows it rather than add a second (the
"Dismiss" borrow picks the banner-dismiss key whose ten translations fit
a dismissible alert). The card's re-check command, run on this branch,
returns exactly the ten exempt literals above and nothing else.
## The census:
`packages/i18n/src/__tests__/ariaLabelLiteralCensus-12145.test.ts`
Home chosen by precedent: `machineLocaleCensus-9909.test.ts` in the same
directory is the repo's existing one-test census over every package's
shipped sources, with comment masking through
`scripts/js-comment-mask.mjs` and declared exemptions cited by content.
This census copies that shape.
- **Population:** every source file under a `src/` tree of a
`packages/*` or `apps/*` workspace, minus tests, stories and tooling.
Walked, not globbed, plus a guard that the files directly under a `src/`
root are in it (see the finding below).
- **Shapes judged:** `aria-label="…"`, `aria-label={'…'}` / `{"…"}` (the
widening triage asked for), a braced template that interpolates nothing,
and the `'aria-label': '…'` object key, when the value carries a letter
(`\p{L}`, so a name in any script counts).
- **Verdict:** each literal is declared by file and name, counted
exactly. A new literal is red, and so is a declared one that disappears,
so its entry leaves in the same commit. The ten exempt sites carry their
reasons; everything else literal is a frozen BACKLOG (next section).
- **Stated blind spots** (in the test header): interpolating templates,
a literal inside a larger expression, `title=` / `placeholder=` /
`sr-only` / renamed props, and JSX text that the comment masker reads as
a comment opener.
Readings (the census as committed, run on each tree):
| Tree | Literal sites found | Undeclared | Verdict |
|---|---|---|---|
| BASE `158c75d` site files, packs and tests at HEAD (Leg A below) | 117
| 20, exactly the card's translated sites | red |
| HEAD `ebfc833` | 97 (10 exempt + 87 backlog) | 0 | green |
## Finding: the card's re-check pathspec never read a file directly
under `src/`
`git grep … -- 'packages/*/src/**/*.tsx'` has no `:(glob)` magic, so its
pattern needs a `/` after `src/`: no file directly under a `src/` root
was ever read (the control: `aria-label="Undo"` in
`plugin-designer/src/DataModelDesigner.tsx` is not returned, while the
same string in `src/components/HistoryPanel.tsx` is). The same regex
with `:(glob)` returned 114 sites on BASE, not 31. This PR translates
the card's enumeration, per the claim's file surface; the 87 literal
sites it did not enumerate (23 files, 11 packages, `plugin-designer` the
bulk of them) are frozen in the census's BACKLOG, so none can grow and
each must leave the list in the commit that translates it. They are
reported to the seat for slicing; `auth`, `layout` and `plugin-map`
among them do not depend on `@object-ui/i18n` today.
## Pins: one `zh` role-and-name query per package, each read against the
pack
- `apps/console/src/pages/docsLoadingName-12145.test.tsx`: the docs
index spinner under en and zh. The spinner is a lucide `svg`, which
testing-library's role table gives no implicit role, so this one is
found by its accessible name (`getByLabelText`).
- `packages/components/src/__tests__/ariaLabelNames.zh-12145.test.tsx`:
`Spinner` (`status`), the data table's row menu trigger (`button`), the
`pagination` renderer's links (`link`), with no provider (en) and under
zh, and the primitive's English defaults absent under zh.
- `packages/fields/src/__tests__/ariaLabelNames.zh-12145.test.tsx`: the
percent slider (`slider`).
-
`packages/plugin-chatbot/src/__tests__/ChatbotEnhanced.composerNames.zh-12145.test.tsx`:
the send button with no host `labels`, and a host label still winning.
-
`packages/plugin-designer/src/__tests__/ariaLabelNames.zh-12145.test.tsx`:
undo / redo (`button`), minimap (`img`), version history (`region`).
-
`packages/plugin-grid/src/__tests__/BulkActionBar.regionName.zh-12145.test.tsx`:
the bar (`region`).
-
`packages/plugin-list/src/components/__tests__/TabBarSelect.triggerName.zh-12145.test.tsx`:
the trigger (`button`).
-
`packages/plugin-detail/src/renderers/__tests__/record-alert.test.tsx`:
one added case, the dismiss button (`button`).
-
`packages/app-shell/src/views/RecordFormPage.listLinkPlural-11733.test.tsx`:
its breadcrumb lookup read the English literal; it now reads
`common.breadcrumb` for the language under test, so its zh-CN case is
the package's zh pin (`navigation`).
Each zh case first asserts the zh and en values differ, or it would pin
nothing.
## Evidence (HEAD `ebfc833`, one locked run, every leg restored)
| Leg | Mutation (landing proven on disk) | Result |
|---|---|---|
| 0, HEAD | none | census + 9 pin files: 10 files, 48 passed |
| A, BASE sites | the 24 site files replaced by their BASE blobs, packs
and tests at HEAD; literal names on disk 0 to 20 | 10 files failed, 13
failed / 35 passed: the census lists exactly the 20 translated sites as
`declared 0, found 1`; every pin's zh case red (`Unable to find
role="region" and name "批量操作"` and the like); the components en case red
(`"Loading…"`) |
| B, one site | `BulkActionBar`'s `t(…)` back to `aria-label={'Bulk
actions'}` (the braced shape) via objectstack's
`scripts/ablation-replace.mjs`: anchor 1 to 0, blob `c9c2d24` to
`05ef07e` | census red (`BulkActionBar.tsx · Bulk actions — declared 0,
found 1`), grid zh pin red |
| C, backlog shrink | `ListView`'s backlog `aria-label="Bulk actions"`
removed (anchor 1 to 0, blob `db03dae` to `11c9b6a`), its entry left |
census red (`ListView.tsx · Bulk actions — declared 1, found 0`) |
After Leg A, `git diff HEAD` was empty and the status clean; Legs B and
C each restored to the HEAD blob with `git diff HEAD` empty; the run
ended with the tree clean at `ebfc833`.
## Gates (all at HEAD `ebfc833`)
- **Build:** the console's dependency closure, `turbo run build
--filter=@object-ui/console^... --concurrency=2`: 34 of 34 tasks; then
the console itself: 35 of 35.
- **Type-check** (`type-check`, each script name echoed): `i18n`,
`components`, `fields`, `plugin-grid`, `plugin-list`, `plugin-chatbot`,
`plugin-designer`, `plugin-detail`, `app-shell`, `console`: all exit 0.
Each new or edited test file is in its package's type-checked program
(`tsconfig.test.json`, or the console's `tsconfig.json`).
- **Tests,** repo-root form: the census and pins above; `packages/i18n/`
plus `scripts/__tests__/check-i18n-call-site-keys.test.ts`: 84 files
passed; the 130 existing test files that import or query a changed
module, in two runs: 69 and 61 files passed.
- **i18n family:** `check:i18n-keys`, `check:i18n-drift` (8 keys added,
0 `en` values changed), `check:i18n-dead-keys` (report; none of the
eight, nor any reused key, listed), `check:i18n-designer-parity`,
`check:eager-locale-catalogues` (only `en` eager),
`check:eager-closure`: all exit 0.
- **Others,** all 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-surface guard: NOT GOVERNED.
- **Lint** on the 34 touched source and test files: 0 errors; 147
warnings, none on a changed line.
- `main` has not moved since BASE `158c75d`, so no merge was needed. No
docs page quotes these names, so none changed.
## Acceptance notes (outside the card, not edited here; handed to the
seat)
- **Interpolating templates** carry English words the census cannot
judge by design, for example ``aria-label={`Dismiss
${notification.title}`}`` in the three notification surfaces, ``Remove
${name}`` in `TagsField` / `SelectionTray`, ``Copy ${label ?? 'value'}``
in two console developer pages. 30 such sites on BASE.
- **Visible English beside a translated name:** the Shadcn
`PaginationPrevious` / `PaginationNext` render visible "Previous" /
"Next" spans, and `SidebarTrigger` (rendered bare by the `header-bar`
renderer) names itself through an English `sr-only` span. Code read
only.
- `ChatbotEnhanced`'s `uploadFiles` fallback is still the English
literal, the same shape as the `submit` fallback fixed here; the console
passes its own label.
- The docs pages' visible headings and empty states are English copy;
out of this card, which is about accessible names.
Session: `https://claude.ai/code/session_016djJF12Qt14ejKR5Vjt4sK`
---
_Generated by [Claude
Code](https://claude.ai/code/session_016djJF12Qt14ejKR5Vjt4sK)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f962cdc commit e723f58
45 files changed
Lines changed: 1112 additions & 30 deletions
File tree
- .changeset
- apps/console/src/pages
- packages
- app-shell/src/views
- components/src
- __tests__
- custom
- renderers
- basic
- complex
- fields/src
- __tests__
- widgets
- i18n/src
- __tests__
- locales
- plugin-chatbot/src
- __tests__
- elements
- plugin-designer/src
- __tests__
- components
- hooks
- pages
- plugin-detail/src/renderers
- __tests__
- plugin-grid/src
- __tests__
- components
- plugin-list/src/components
- __tests__
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| 30 | + | |
29 | 31 | | |
30 | 32 | | |
31 | 33 | | |
| |||
37 | 39 | | |
38 | 40 | | |
39 | 41 | | |
40 | | - | |
| 42 | + | |
41 | 43 | | |
42 | 44 | | |
43 | 45 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
43 | 44 | | |
44 | 45 | | |
45 | 46 | | |
| 47 | + | |
46 | 48 | | |
47 | 49 | | |
48 | 50 | | |
| |||
54 | 56 | | |
55 | 57 | | |
56 | 58 | | |
57 | | - | |
| 59 | + | |
58 | 60 | | |
59 | 61 | | |
60 | 62 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
174 | 174 | | |
175 | 175 | | |
176 | 176 | | |
177 | | - | |
| 177 | + | |
178 | 178 | | |
179 | 179 | | |
180 | 180 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
| 27 | + | |
26 | 28 | | |
27 | 29 | | |
28 | 30 | | |
| |||
31 | 33 | | |
32 | 34 | | |
33 | 35 | | |
34 | | - | |
| 36 | + | |
35 | 37 | | |
36 | 38 | | |
37 | 39 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
70 | 71 | | |
71 | 72 | | |
72 | 73 | | |
| 74 | + | |
73 | 75 | | |
74 | 76 | | |
75 | 77 | | |
| |||
97 | 99 | | |
98 | 100 | | |
99 | 101 | | |
100 | | - | |
| 102 | + | |
101 | 103 | | |
102 | 104 | | |
103 | 105 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| 39 | + | |
39 | 40 | | |
40 | 41 | | |
41 | 42 | | |
| |||
85 | 86 | | |
86 | 87 | | |
87 | 88 | | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
88 | 92 | | |
89 | 93 | | |
90 | 94 | | |
| |||
119 | 123 | | |
120 | 124 | | |
121 | 125 | | |
122 | | - | |
123 | | - | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
124 | 131 | | |
125 | 132 | | |
126 | 133 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
329 | 329 | | |
330 | 330 | | |
331 | 331 | | |
332 | | - | |
| 332 | + | |
333 | 333 | | |
334 | 334 | | |
335 | 335 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
0 commit comments