Repository navigation
[Maintainer] i18n: migrate tools and toolbar - #1171
Conversation
…ale audit - scripts/i18n-literals.ts: shared scanner for user-visible literals, a per-file ratchet (i18n/literal-baseline.json) wired into i18n:check, and report/update commands; exemptions via i18n/literal-allowlist.json or an i18n-ignore comment - scripts/i18n-codemod.ts: rewrites JSX text, UI props, conditional and template copy and notification payloads into i18n._ descriptors with the usePascalI18n hook; module-level and server copy is listed for a person - en-XA pseudo-locale compiled from English, registered by the app outside production, plus a Playwright audit that screenshots surfaces and lists copy that bypassed the catalog - i18n:check accepts AI drafts flagged fuzzy + Review: required (they are never compiled into packs); check --reviewed keeps the sign-off gate
…rface helpers; codemod accepts explicit files
Localizes the project navbar, share panel, app header, the viewport view bar (3D/2D tabs, split, ground mode, more-views menu) and the viewer toolbar with its Display menu. Module option lists become lazy descriptors; scene theme names share one helper with the viewer controls. zh-CN/fr/es/ja drafts are fuzzy and await human review.
…view A translation that is Review: human on the base branch with the same English must stay reviewed. CI checks out the merge commit's first parent for the comparison, and release.yml runs check --reviewed before publish. The review rules are now a pure function so tests use fixtures.
The baseline lists each file's legacy copy by text, so new or swapped copy fails, and CI fails when the baseline gains entries relative to the base. i18n-ignore needs a reason; hyphenated labels and string constants rendered in JSX are copy; toast descriptions count once. The codemod keeps one-line bodies valid and converts explicitly named .ts modules. Enter, Space, Tab and Del leave the allowlist.
…rds; playwright-core 1.64.1
Scene theme names move to a shared helper used by both toolbars. 64 new messages; zh-CN/fr/es/ja drafts are fuzzy and await human review.
A translation that appears without a Source-Hash becomes a fuzzy draft unless a person already marked it Review: human, so pasted or tool-filled copy cannot reach the compiled packs without review.
Server pages render AppHeader, so the usePascalI18n hook there returned a 500 on the home page (cli-smoke).
They can render inside server components, where an inserted hook crashes the request.
A const is flagged only when the nearest declaration of the rendered name is that const string, so shadowing parameters, locals and same-named constants elsewhere in the file no longer swap the copy.
…eaks release.yml runs release-check instead of check --reviewed: it compares the committed catalogs with the last v* tag and fails when a reviewed entry with unchanged English became a draft, or when a compiled pack contains a draft. i18n:check runs the same checks against the PR base in CI. Both write per-locale draft counts to the job summary. Job-level tests run the command against temporary catalog copies.
Moved, split or extracted copy no longer fails: each text the baseline adds
must consume an equal text removed elsewhere, duplicates counted. JSX text is
recorded as it renders (whitespace, full HTML entities via entities,
{' '} joins), constants inside sentences become part of the sentence, and
concatenated copy is a candidate. The allowlist may not gain entries
relative to the base. Named callbacks and async functions are not hook
owners.
…dempotent --vars Copy is encoded so the compiled message renders the original text. Hooks are never placed in named callbacks or async functions, and editor package entry modules without 'use client' are left for a person. Descriptor mode stops at existing descriptors and i18n._ calls.
…ent.before A reviewed ID replaced by a new ID with the same English needs its translation carried over or reviewed again. On push, CI fetches and compares with the commit before the push.
…s for the new guards
Below sm their text is hidden, so both buttons carry the translated aria-label; the navbar-mobile audit surface checks names at 390px.
…rror extract marks a translation whose English changed fuzzy + Review: required and keeps the old Source-Hash; that entry is never shipped, so ordinary CI counts it as awaiting review. A reviewed entry with a stale hash, and every stale entry under --reviewed, still fail.
Releases tag packages @pascal-app/<pkg>@<version>, so the v* lookup found v1.0.3, which has no catalogs, and the check could never fail. The base is now the latest @pascal-app/editor@* tag (the package that ships the catalogs); when that tag predates the catalogs it falls back to origin/main (another commit with catalogs), then to the commit that introduced them, and otherwise fails loudly. Tests run the default lookup in a temporary repository and fail on a reviewed-to-draft change.
- Same-English matches between a removed reviewed ID and a new ID are a
warning (console and job summary), since they may be unrelated additions.
A rename declared with a renamed-from: <old id> descriptor comment is
carried over by i18n:extract and fails CI only if it arrives without the
reviewed translation.
- release-check fails on a --base that cannot be resolved or read.
- The allowlist applies to whole joined sentences; constants inside
concatenations count as copy; parenthesized named callbacks are not hook
owners.
- The codemod encodes adjacent static pieces together, keeps no-break
spaces, encodes text before a placeholder knowing a { follows, decodes
attribute entities, and escapes CR and line separators in emitted strings.
- The baseline format is version 3, since fingerprints now use rendered
text.
A previous-line ignore applies only when that line is a standalone comment, so an exemption written after one literal no longer hides a new literal on the following line.
Node definitions stay English for MCP and agents. The editor now looks their presentation labels and descriptions up as node.<kind>.label and node.<kind>.description, and tool hints, gesture keys and chip labels as editor.toolHint.*, with the English as fallback. i18n:extract generates packages/nodes/src/i18n/definition-messages.ts from the built-in definitions so those IDs are extracted, and i18n:check fails when it drifts. Plugin kinds can register the same IDs.
Tool tiles, sections, search, roof types and features, fence features, MEP and kitchen browser. Registry tools use the node label lookup; the XR plugin still receives English, since it renders plain labels itself.
Select/Delete, measurement and construction-dimension menus, camera actions, point-and-ask, follow Pascal, references (scan and guide counts are ICU plurals), floating action menu counts, and cursor icons. Action buttons carry their label as the accessible name.
HUD titles (including Move/Edit/Split {kind}), hint rows, snapping, grid
step and continuation chips, the Shift bypass hint, key and mouse-button
names, and the camera controls hint. Grid steps use locale length
formatting.
614 messages; zh-CN/fr/es/ja drafts are fuzzy and await human review. The pseudo-locale audit covers the build tab, MEP tools, tool dock, camera hint, wall and door HUDs, and the measurement and reference menus.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
i18n:check runs before the build in CI, where @pascal-app/* resolve to missing dist/ files. The generator now loads the built-in definitions in a child Bun process with a tsconfig whose paths map every workspace package export to its source.
|
Tooling cross-review at 4176867: APPROVE I reviewed only this PR's own diff on top of #1169 (70f7702, which already contains #1167), checked out locally at /tmp. CI is green at the head: quality, ci, cli-smoke, Changed paths and Bugbot all pass.
P2 (doesn't block)
Stacking: this PR contains #1169 and #1167. After they merge, it needs main merged in or a rebase, and a re-check. |
The hint label is a message descriptor now, so comparing it with the English 'Draw the mezzanine' never matched and the opening HUD showed the mezzanine copy.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 104b9c7. Configure here.
| onClick={() => cyclePaintScope()} | ||
| shortcut="Shift" | ||
| tooltip={`${verb} scope — click or press Shift to cycle`} | ||
| tooltip={i18n._({ id: 'editor.helpers.contextualHelperPanel.verbScopeClickOrPressShift', message: '{verb} scope — click or press Shift to cycle', values: { verb } })} |
There was a problem hiding this comment.
Paint scope verb stays English
Low Severity
The paint-scope chip builds verb as hardcoded English (Pick / Erase / Paint) and interpolates it into the new localized tooltip, plus the chip label and aria-label. Non-English locales get a mixed-language sentence that translators cannot fix, because {verb} is a value rather than a translatable message.
Reviewed by Cursor Bugbot for commit 104b9c7. Configure here.
| /> | ||
| <span className="min-w-0 flex-1 truncate">{levelName}</span> | ||
| <span className="text-[10px] text-muted-foreground">{index + 1} below</span> | ||
| <span className="text-[10px] text-muted-foreground">{index + 1}{' '}{i18n._({ id: 'editor.actionMenu.viewToggles.below', message: 'below' })}</span> |
There was a problem hiding this comment.
Below count ignores word order
Low Severity
The reference-floor list renders the offset as a concatenated number plus a standalone below message. Translators cannot change word order, plural form, or classifier, so locales that need the number inside the phrase (or a different order) show an ungrammatical label.
Reviewed by Cursor Bugbot for commit 104b9c7. Configure here.
|
Tooling cross-review at 104b9c7: APPROVE I re-confirmed my approval at 4176867 against this head, checked out locally at /tmp.
P2 (doesn't block)
Security isn't needed. |


What does this PR do?
Localizes the editor's tools and toolbar surfaces. Part of #621.
packages/editor/src/i18n/node-messages.tslooks upnode.<kind>.label/node.<kind>.description, andeditor.toolHint.*for hint labels, gesture/key names and chip labels. The English is the fallback, so a plugin kind without messages shows its own English; a plugin can ship the same IDs withregisterMessages.{value}.i18n:extractgeneratespackages/nodes/src/i18n/definition-messages.ts(/* i18n */descriptors for every built-in kind's label, description, tool and affordance hints, keys and chips) so these IDs are extracted.i18n:checkfails when that file drifts.apps/editor): tool tiles (base tools and registry kinds), sections, search (matches the translated names), roof types and features, fence features, MEP, the kitchen browser and its palette commands. The XR wand panel model is translated too. The external XR plugin'scollect*bindings still receive English, because that plugin renders plain labels itself.ssuffixes, and the reference level name uses the localized level name.Move / Edit / Split {kind}with the translated kind name;ShortcutToken;ActionButtons now carry their label as the accessible name. Follow Pascal had none.furnishToolslabels andHudTitle.labelare nowMessageDescriptors.getSnappingModeLabelkeeps returning English, because the external XR plugin reads it.fuzzy+Review: required(AI,AI-Provider: cursor-agent), shown in English until a fluent reviewer approves. The literal baseline drops from 2,843 to 2,592.build-tab,mep-tools,tool-dock,camera-hint,wall-tool-hud,door-tool-hud,measure-menu,references-menu. They usedata-surfacemarkers on the build tab and the dock.How to test
bun run i18n:check: passes and reports 721 entries per locale awaiting review (107 already onmainplus 614 here).bun dev, open a project with?lang=en-XA: the build tab, tool dock, HUD (arm Rectangle or Door) and camera hint are bracketed and accented. Arm the wall split or a window to see chip labels.bun run i18n:pseudo-audit -- build-tab mep-tools tool-dock camera-hint wall-tool-hud door-tool-hud measure-menu references-menu: all eight PASS, with no untranslated strings and no unnamed controls. I ran this; screenshots below.bun --cwd packages/editor run test: 2,669 pass, including the updated HUD, contextual-help and wall-variant tests (before the opening fix).bun --cwd apps/editor run test: 74 pass.bun test ./scripts/i18n.test.ts ./scripts/i18n-literals.test.ts: 53 pass.Still English after this PR
Roadfrom@pascal-app/plugin-streetscape.nodesand belong to the furnishing PR.Screenshots / screen recording
en-XA(accented, ~30 % longer, bracketed), captured byi18n:pseudo-auditat 1440×900.Build tab:
Build tab en-XA
MEP tools under Advanced (
Roadis the streetscape plugin's own kind):MEP tools en-XA
Wall (Rectangle room) HUD with snapping and grid chips:
Wall HUD en-XA
Door HUD with type/style chips from the definition:
Door HUD en-XA
Measurement menu in the tool dock:
Measure menu en-XA
References popover:
References en-XA
Tool dock and camera hint:
Tool dock en-XA
Checklist
bun devbun checkto verify)mainbranchTo show artifacts inline, enable in settings.
Note
Medium Risk
Broad UI string migration with new MessageDescriptor types on public configs; behavior should be unchanged in English but search/HUD/plural formatting paths are touched across many interaction surfaces.
Reviewed by Cursor Bugbot for commit 104b9c7. Bugbot is set up for automated code reviews on this repo. Configure here.