Skip to content

[Maintainer] i18n: migrate left rail, level selector and layers panel - #1173

Merged
Aymericr merged 10 commits into
mainfrom
cursor/i18n-left-panels-6d7c
Oct 27, 2026
Merged

Aymericr merged 10 commits into
mainfrom
cursor/i18n-left-panels-6d7c

Conversation

@Aymericr

@Aymericr Aymericr commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

What does this PR do?

Localizes the left side of the editor: the icon rail, the layers (site) panel, the floating level selector and the level dialogs. Part of #621.

  • Icon rail. The app's tabs (Scene, Build, Paint, Items, Settings) become descriptors, translated once in SceneEditor. That covers the rail tooltips and aria-labels, the mobile tab bar and the panel headers. The editor's own Site, Settings and Plugins entries are translated too, and a crashed plugin panel shows translated fallback copy.
  • Layers panel (site-panel):
    • section headers, empty states, the level and building rows, units, rooms and zones, and the camera snapshot menus;
    • the upload button, as one ICU select per asset type;
    • the selection badge, as an ICU plural.
  • Layer row names. Unnamed rows used to show hard-coded English kind names ("Dormer", "Solar Panel", "Wall"…). They now use the existing node.<kind>.label messages through nodeLabelDescriptor, so they share translations with the build tab. The registry fallback does the same with presentation.label. Generated names ("Room (12 m²)", "Roof (2 segments)", "Gable (4.0×6.0m)", "Flight / Landing") live in site-panel/default-names.ts as whole ICU messages, with plurals and a roof-type select. The drag preview uses the same names.
  • Level names in the tree, the unit rows, the duplicate dialog and the delete confirmation use localizedLevelName, so they read "Ground floor / Floor 1", not the core English helper's output.
  • Level rename. Both rename fields start empty when the level still has its generated default name, instead of prefilling the stored English. The localized default shows as the placeholder (authoredLevelName), and leaving the field unchanged writes nothing.
  • Floating level selector. Covers add, insert, reorder, the height popover, the row menu, ground-line labels and titles, unit focus, and the delete confirmation. A new messageWithSlot helper keeps that confirmation one translatable sentence around the bold level name.
  • Duplicate level dialog. The presets are lazy descriptors, and the description is a whole sentence with {levelName}.
  • Zone and items panels. Their copy (source chips, search, empty states) is converted while they're here.
  • Accessibility. Fixes the unnamed controls the audit flagged:
    • expand/collapse chevrons get labels and aria-expanded;
    • the rename pencil, the room-row delete button and the colour dots get names;
    • the level option buttons get "{levelName} options".
  • Overflow. The level menus in the selector and the tree now grow with their content (w-max, up to max-w-72) instead of wrapping longer translations.
  • Codemod fix. The codemod no longer adds a second usePascalI18n import when the file already imports it in another @pascal-app/core import (with a test).
  • Server-component guard. site-panel/index.tsx gets 'use client'. It already called hooks 57 times, but the codemod's guard read it as a possible server component because the editor barrel re-exports it.
  • Catalogs. 152 new messages. zh-CN/fr/es/ja drafts are fuzzy + Review: required (AI-Provider: cursor-agent), so they show English until a fluent reviewer approves them. The literal baseline drops from 2,592 to 2,436.
  • Audit surfaces. New surfaces icon-rail, layers-panel, level-selector, level-menu, duplicate-level-dialog and delete-level-dialog. They run on a project seeded from the MCP two-bedroom template, so the tree and level list have rows. The template's room names are listed as data.

How to test

  1. bun run i18n:check: passes and reports the drafts per locale awaiting review.
  2. bun dev, then open a furnished project with ?lang=en-XA. The rail tooltips, the Scene panel tree (expand a level and walls), the level pill, its ⋮ menu and both level dialogs should all be bracketed and accented. Rename a level that still has its default name: the field starts empty with the localized name as placeholder.
  3. bun run i18n:pseudo-audit -- icon-rail layers-panel level-selector level-menu duplicate-level-dialog delete-level-dialog: all six PASS, with no untranslated strings and no unnamed controls. I ran this, and the full audit: every surface from the earlier PRs still passes. Screenshots below.
  4. Tests I ran:
    • bun test src in packages/editor: 2,670 pass.
    • After the merge with main: the src/i18n and sidebar tests (24 pass, including the new authoredLevelName test) and bun test scripts/ (130 pass).
    • bun test lib in apps/editor: 74 pass.
    • Type checks for the editor and the app are clean, and bun run check reports no findings in the changed files.

Still English after this PR

  • Plugin rail entries (Nature, Environment, WebXR, Pools, Landscape, Bath Space, Streetscape). External plugin packages register these labels, so they're listed as data on the icon-rail surface.

  • Row names a definition's tree.label returns. That is plugin and definition API, which returns English.

  • Base-plate footprint names ("Shared floor", "Kitchen floor"). These come from core floorFootprintName, which MCP also reads; [Maintainer] i18n: migrate properties and inspector panels #1174 adds the structured variant.

  • Unit formatting:

    • the "8 ft / 9 ft / 10 ft" height presets, which stay in the literal baseline;
    • the fixed m in roof and stair segment sizes;
    • toFixed in area and perimeter.

    These belong to the units and number-formatting PR.

  • Other pages and surfaces: the settings panel (its own surface PR), the home and settings pages, and the WebXR "Enter VR" button. These still fail the home, settings and whole-editor audit sweeps.

Screenshots / screen recording

en-XA (accented, about 30 % longer, bracketed), captured by i18n:pseudo-audit at 1440×900.

Layers panel on the two-bedroom template:
Layers panel en-XA

Icon rail with the Items tooltip:
Icon rail en-XA

Level selector with two levels:
Level selector en-XA

Level row menu (one line per item after the width fix):
Level menu en-XA

Duplicate level dialog:
Duplicate level dialog en-XA

Delete level confirmation (one sentence around the bold level name):
Delete level dialog en-XA

The "2 Issues" badge in the screenshots is the Next dev overlay. Its React "synchronously unmount a root" console error also appears on main with the same furnished scene.

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

Note

Low Risk
Mostly string and accessibility copy changes with memoized tab labels; scene graph and level operations behavior are unchanged aside from how names are shown and edited.

Reviewed by Cursor Bugbot for commit e52d5da. Bugbot is set up for automated code reviews on this repo. Configure here.

…ialogs

Converts the icon rail, plugin panel shell, site/layers tree, zone and items
panels, the floating level selector and the duplicate-level dialog to runtime
descriptors. Layer rows fall back to the node kind's translated label and
localized level names; generated default names (room, slab, roof, stair...)
and counts use whole ICU messages. messageWithSlot keeps the delete-level
confirmation one sentence around the bold level name.
…s fit translations

Expand/collapse chevrons, rename pencils, row delete buttons, colour dots and
level option buttons get accessible names; the level menus grow up to max-w-72
instead of wrapping longer translations.
…nd level dialog surfaces on a furnished scene
@Aymericr Aymericr added the bot:maintainer label Oct 27, 2026 — with Cursor
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-27T07:55:06.305743Z 5bbbf00 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5bbbf006a6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/editor/components/scene-loader.tsx Outdated
// biome-ignore lint/correctness/useExhaustiveDependencies: labels re-translate when the locale changes
const sidebarTabs = useMemo(
() => SIDEBAR_TABS.map((tab) => ({ ...tab, label: i18n._(tab.label) })),
[i18n, locale],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recompute sidebar labels after catalog refreshes

When a host registers or overrides messages after SceneEditor mounts—such as the documented asynchronous registerMessages path—usePascalI18n() triggers a render but retains the same i18n object and locale string. This memo therefore reuses the old labels, leaving the desktop rail and mobile tabs in fallback English or with stale overrides while the rest of the UI updates. Translate these descriptors on each render or include the i18n catalog version in the dependencies.

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/editor/src/components/ui/floating-level-selector.tsx
…evel rename placeholder

Keying the tab-label memo on the catalog version picks up messages registered
after mount, not only locale switches. The floating selector's inline rename
placeholder now matches the localized row name.

Copy link
Copy Markdown
Member Author

Tooling cross-review at b5e932d: APPROVE

I checked this locally at /tmp, at the head commit (branched from main at ee8d567). Required CI is green: quality, cli-smoke, Changed paths and Bugbot all pass. mcp-ci doesn't run because no core, MCP or scene-API files change.

  • Lingui and drafts
    • The left rail, level selector, level dialogs, layers tree, zone panel and items panel all go through i18n._ or descriptors.
    • Default layer names are whole ICU messages in default-names.ts, with plurals and a roof-type select that covers all 9 RoofType values. messageWithSlot keeps bold names inside one message.
    • Drafts go from 721 to 873 per locale (+152). Review: human stays at 85 per locale, exactly as on main, so no new entry is marked reviewed.
  • Gates
    • i18n:check passes, with literal candidates down from 2592 to 2436.
    • release-check passes against origin/main.
    • literal-baseline.json only loses entries. The one + line is the existing "9 ft" losing its trailing comma.
    • literal-allowlist.json is unchanged, and the PR touches no workflows or guard scripts. The only script change is in i18n-codemod.ts.
  • The audit surfaces are real. I ran i18n:pseudo-audit against a local dev server. icon-rail, layers-panel, level-selector, level-menu, duplicate-level-dialog and delete-level-dialog all pass, and the layers screenshot shows the furnished two-bedroom scene.
    • Changing the dialog's Duplicate button back to a plain literal fails duplicate-level-dialog with "Duplicate" untranslated.
  • Codemod fix: the new test fails with main's i18n-codemod.ts (3 usePascalI18n matches instead of 2) and passes with the fix.
  • Behaviour
    • packages/editor passes all 2670 tests.
    • The ground-offset tooltip still uses toDisplay(ground.offset).
    • The level rename placeholder uses localizedLevelName, which also covers Bugbot's Low.
    • The sidebar tab labels now recompute on i18n.getVersion(), which covers Codex's P2 about late registerMessages.
    • The English output is unchanged except for one small change, listed below.

P2 (doesn't block)

  • The default roof-segment row name used x (Hip (4.0x6.0m)), while its drag preview used ×. Both now show ×, so the English text changes slightly.
  • use-plugin-panels.tsx has unformatted lines (return ((). Biome skips components/ui, so CI doesn't catch it.
  • The audit scenes log "Attempted to synchronously unmount a root while React was already rendering". main logs the same error on the same scene, so it isn't caused by this PR.

Security isn't needed.

Conflicts in the literal baseline and compiled catalogs resolved by
regenerating them with i18n:extract and i18n:literals update.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7a5b160. Configure here.

Comment thread packages/editor/src/components/ui/floating-level-selector.tsx
… stored English

Bugbot (Low): localizedLevelName re-translates stored defaults like
"Ground floor", but both rename fields prefilled that English. They now edit
authoredLevelName, with the localized default as the placeholder, and leaving
the field unchanged writes nothing.

Copy link
Copy Markdown
Member Author

Tooling cross-review at e52d5da: APPROVE

I re-confirmed my approval at b5e932d against this head, checked out locally at /tmp. Required CI is green: quality, cli-smoke, Changed paths and Bugbot all pass.

  • The main merge (7a5b160) adds only regenerated files
    • I redid the merge of b5e932d with main (c0284b6) myself. Only the literal baseline and the compiled en and en-XA packs conflict, and every other file auto-merges.
    • I then ran bun run i18n:extract and bun run i18n:literals -- update. The result is byte-identical to 7a5b160.
  • No reviewed entry lost, baseline doesn't grow
    • A script comparing the catalogs against main shows 152 new entries per locale, none of them reviewed. Reviewed entries stay at 85 per locale, none is lost, and no reviewed msgstr changed.
    • The baseline goes from 2589 to 2433 and adds no text compared with main.
  • Bugbot fix e52d5da is correct
    • authoredLevelName treats a stored Level N or the English default as generated. The floating selector and both layer-panel rename fields now start empty, with the localized placeholder.
    • Saving without typing changes nothing, because (trimmed || undefined) !== authoredName is false. So a stored name that matches the English default is never wiped, and an authored name like Mezzanine edits as Mezzanine.
    • Other nodes keep their behaviour, since editableName is optional. InlineRenameField also no longer writes when nothing changed (it now treats an empty string and undefined as equal).
  • Checks: i18n:check passes (879 drafts per locale, 2433 candidates). release-check passes against origin/main. packages/editor passes all 2671 tests.

P2 (doesn't block)

  • The new test fails without the fix, but only because the authoredLevelName export is missing. It tests the helper, not the rename field. A component test that opens a rename on a level stored as Ground floor and checks for an empty value would guard the behaviour itself.

Security isn't needed.

@Aymericr
Aymericr merged commit 3d14690 into main Oct 27, 2026
4 checks passed
cursor Bot pushed a commit that referenced this pull request Oct 27, 2026
slab-tree-node keeps both sides (localized footprint name and slab default
name); the audit manifest keeps both surface sets. Catalogs and the literal
baseline are regenerated with i18n:extract and i18n:literals update, with
#1173's drafts carried over by ID.
@Aymericr
Aymericr deleted the cursor/i18n-left-panels-6d7c branch October 27, 2026 12:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants