Skip to content

[Maintainer] i18n: migrate properties and inspector panels - #1174

Merged
Aymericr merged 24 commits into
mainfrom
cursor/i18n-inspector-6d7c
Oct 27, 2026
Merged

Aymericr merged 24 commits into
mainfrom
cursor/i18n-inspector-6d7c

Conversation

@Aymericr

@Aymericr Aymericr commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

What does this PR do?

Localizes the properties and inspector panels: the shared panel shell, the editor's own panels, the auto inspector, the building-structure inspectors and the floor footprint names. Part of #621. main (23bdaf4) is merged in.

Codex round 3 (at 585db60): room-name edits, new design

The reverse lookup across all translations is gone.

  • Picks. A suggestion pick still stores the English catalog name, numbered.
  • Panel edit. An edit stores English only when the room's stored name is a catalog name (optionally with " N") and the edit is that same entry's translated label with the number changed, added or removed. It uses the room's own entry, so in French "Salon 3" on a Lounge stores "Lounge 3" and "Bureau 2" on a Study stores "Study 2", even though Living room and Office share those labels.
  • Free text. Everything else is stored exactly as typed: a click on 'Use "…"', an exact translation such as "Cuisine", another room's translation ("Salon 2" on a Bedroom), and any other text. Surrounding spaces are kept as typed, as main keeps them ("Chambre 3 " stores "Bedroom 3 ", like "Bedroom 3 " in English), and an unchanged padded name writes nothing.
  • 'Use' row. It is hidden again when the text exactly matches an English catalog name, as on main, so English offers only "Bedroom 2" next to a Bedroom. Enter on the typed row commits like blur; only a click uses the text literally. A label two names share is never treated as an exact match for Enter.
  • Tests. room-name-storage.test.ts drives the panel's own commitRoomNameEdit and commitRoomNamePick (moved out of RoomName, which calls them, unchanged-field guard included), and compares French with the same keystrokes in English. It covers Lounge/Salon and Study/Bureau, the 'Use' click, "Cuisine", trailing and surrounding spaces, and unchanged fields.
  • Merge. main at 23bdaf4 is merged in. i18n:extract and the literal baseline needed no changes.

Verify round (Codex at 6e727bf, Tooling)

  • Partially translated packs. Filtering matches the English name as well as the label, and an exact match becomes the active row (superseded in part by round 3 above).
  • Viewer labels. The breadcrumb, sidebar and hover label get the editor's i18n passed in (no global shim), and their memo keys include the locale and catalog version. The viewer still imports nothing from the editor.
  • Prototype keys. Catalog lookups use Object.hasOwn, so rooms named constructor, toString, __proto__, hasOwnProperty or valueOf 3 are free text. Each has a test.
  • Nits. formatFixed rounds like toFixed and shows -0 as 0. Opening and hidden-panel counts are ungrouped ("1000 openings"). The pseudo-audit roof and stair row matches are anchored regexes.

Review round (Codex audit and Tooling)

  • Room names (P1). Picking a suggestion now saves the English catalog name in every UI language, numbered against the level's rooms. In French, picking "Chambre" next to an existing Bedroom saves "Bedroom 2", exactly what English saves. Typed text is saved as written.
    • The combobox shows and searches translated labels.
    • displayRoomName() shows catalog names translated with their number ("Bedroom 2" → "Chambre 2") wherever a room is named: room panel title, field and kickers; layers rows; unit and zone lists; 2D/3D room pills; viewer breadcrumb, sidebar and hover; walkthrough HUD; node display; footprint names; floor-opening kicker; shared-wall notes; unit members; zone quantities.
    • Tests cover a French pick saving the same value as an English one, label search, typed text, and display.
  • Stored data. Scenes keep exactly the English names main writes today. Nothing migrates or rewrites existing scenes:
    • the layers rows' rename field still edits the stored name;
    • display paths never write;
    • floor-plan builders, exports and agent context keep the stored English;
    • the zone's own 3D label (a nodes renderer the standalone viewer also uses) keeps the stored name, so the viewer doesn't depend on the editor.
  • Window-merge refusals (P2). These now use an ICU select on the blocking node's type, with one whole sentence per kind. English reads "There is an item between them." again.
  • Number formatting (P2). Run lengths, hole areas, curtain-wall spacing and effective thickness use the locale formatter instead of toFixed.
  • Room opening counts (P2). These are one ICU plural, keeping "No openings".
  • Nits. "1 custom panel is outside this grid. It returns…"; an unnamed room reads "Opening · in Room" again.
  • formatList. English keeps the original comma list ("Kitchen, Hall"). Other locales use Intl.ListFormat for their own separators (Japanese 、, French "et"). The doc comment says so, with a test.
  • Audit browser. Pinned to UTC and en-GB, so the editor defaults to metric and accepted data such as areas match on every machine. I ran the audit with TZ=America/New_York.
  • Merge with main. slab-tree-node.tsx keeps both sides, and the audit manifest keeps both surface sets. Catalogs and the literal baseline are regenerated with i18n:extract and i18n:literals update, with [Maintainer] i18n: migrate left rail, level selector and layers panel #1173's drafts carried over by ID.

Tooling follow-ups (from the first round)

  • Node-messages check. It skips a missing packages/nodes/src/index.ts only in the tests' temporary catalog projects (I18N_ROOT). In a real checkout it fails extract and check. Covered by a test.
  • Roof-segment name: × kept. The x only ever appeared in the roof row's default name; its drag preview already used ×, and so do the stair rows. See the screenshots below.

Inspector migration

  • Floor footprint names.
    • floorFootprintNaming returns the name's parts.
    • floorFootprintName builds the same English from them, so MCP, agents and core messages don't change.
    • The editor words the parts in the UI language.
  • Auto inspector (def.parametrics).
    • Group and action labels, field names and enum options are translated at display time with shared editor.inspector.* IDs.
    • The definition-messages generator emits them for every built-in kind.
    • Plugin kinds keep their English.
  • Editor panels. Room, floor & foundation, floor opening, reference, multi-selection, multi-height, mobile sheet and selection bar, collections, the panel shell and node display names. English plural helpers are now ICU plurals, and split sentences use messageWithSlot.
  • Node inspectors.
    • Covered: wall and curtain wall, slab, ceiling, door, window, roof, roof segment and glazing, stair, stair segment with railing and construction controls, site, unit, item, zone quantities and system check.
    • Door and window style and type cards, wall reference options and stair diagnostics get display-time descriptors. Core's English stays for chips and agents.
  • Accessibility and overflow. The panel's back, reset and close buttons and the finish swatch are named, and the mezzanine shape row wraps.
  • Catalogs. zh-CN, fr, es and ja drafts are fuzzy with Review: required, so they show English until reviewed.
  • Audit. Nine inspector surfaces, opened from layers rows on a two-bedroom project seeded with a roof and a stair.

How to test

  1. bun run i18n:check passes and reports 2,523 entries per locale awaiting review, and 1,399 legacy literal candidates.
  2. Run bun dev with ?lang=en-XA.
    • Select rooms, walls, doors, windows, a roof, a roof segment, a stair and a flight from the Scene panel. Turn on a room's floor and open Sits on. Every inspector should be bracketed and accented.
    • Rename a room from its suggestions next to an existing Bedroom. The scene stores "Bedroom 2", and the UI shows the pseudo-localized "Bedroom" plus "2". Change the number in the field and blur: the scene stores "Bedroom 3".
  3. Run TZ=America/New_York bun run i18n:pseudo-audit. Every migrated surface passes: navbar through MEP tools, the nine inspectors, and [Maintainer] i18n: migrate left rail, level selector and layers panel #1173's six left-panel surfaces. home, settings and the whole-editor sweep still fail on pages not migrated yet. I ran this.
  4. Tests I ran:
    • packages/editor: 2,700 pass.
    • packages/nodes: 5,025 pass.
    • packages/core: 6,162 pass.
    • apps/editor: 74 pass.
    • bun test scripts/: 131 pass.
    • Type checks are clean.

Still English after this PR

  • Other inspectors. Cabinets, column, chimney, elevator, construction dimension, block, vents, cupola, dormer, skylight, solar panel, fences and MEP. These are the next inspector PR.
  • External plugins. Inspector extensions from external plugins, such as landscape's "Retaining wall".
  • Command messages. Room-construction refusals and floor notices, for the toasts and errors pass.
  • Room names in unmigrated English sentences. The delete confirmation, item placement notices and point-ask chips still use stored names; each gets displayRoomName when its copy is migrated.
  • Material names. The catalog material names.
  • Unit formatting. "8 ft" presets and fixed m and mm suffixes, for the units PR.

Screenshots

en-XA, captured by i18n:pseudo-audit at 1440×900 (UTC, metric). The images are served from the image-only branch cursor/i18n-audit-screenshots-6d7c, which is never merged.

Room (layers rows show catalog names translated with their number):
Room panel en-XA

Floor & foundation (title is the localized footprint name):
Floor and foundation en-XA

Wall:
Wall panel en-XA

Door:
Door panel en-XA

Window:
Window panel en-XA

Roof:
Roof panel en-XA

Roof segment:
Roof segment panel en-XA

Stair:
Stair panel en-XA

Stair segment:
Stair segment panel en-XA

Layers panel after the merge with #1173:
Layers panel en-XA

Roof-segment row × confirmation, English then en-XA:
Roof and stair rows English
Roof and stair rows en-XA

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
Open in Web Open in Cursor 

…log projects

Outside an I18N_ROOT test project a missing packages/nodes/src/index.ts
now fails extract and check instead of silently dropping the definition copy.
…tList

floorFootprintName keeps its English for MCP and agents; the editor words the
same parts (named, "<room> floor", Shared floor, Floor area) in the UI
language. formatList joins names with the locale's separators.
Group and action labels, the field names prettified from keys and enum
options get editor.inspector.* IDs (slug + hash), shared across kinds; the
definition-messages generator emits them for every built-in kind.
Room, floor & foundation, floor opening, reference, multi-selection and
mobile panels, and the wall, curtain wall, slab, ceiling, door, window, roof,
roof segment, glazing, stair, stair segment, site, unit, zone quantities and
system check inspectors. Floor footprint names, level names and room-name
suggestions show in the UI language; English plural helpers become ICU
plurals and split sentences become whole messages.
Floor top, hole rows, flight measurements, curtain-wall cells, contour summary,
the roof's storey note, glazing grid counts and "Currently …" readouts are
whole messages with values and ICU plurals.
…anels render

Stair railing and construction controls, roof glazing controls, item script
parameters and wall actions are converted. Door and window style and type
cards, wall reference options and stair diagnostics get display-time
descriptors (core's English stays for chips and agents); the room subline is
an ICU plural; the panel's back, reset and close buttons and the finish swatch
are named; the shape-choice row wraps instead of overflowing.
@cursor

cursor Bot commented Oct 27, 2026

Copy link
Copy Markdown

This PR is too large for Bugbot to review. It changes 93,089 lines and 3,728,980 characters. Split the change into smaller pull requests to get a review.

@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-27T10:49:53.814206Z c43f071 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.

Copy link
Copy Markdown
Member Author

Tooling cross-review at c43f071: APPROVE

I reviewed the code outside the catalogs in a local checkout and checked the catalogs by script. Required CI is green: quality, cli-smoke, Changed paths, plus ci and mcp-ci. Bugbot skipped the PR as too large, so it has no automated code review.

Catalogs (checked by script against main c0284b6)

  • Each of fr, es, ja and zh-CN gets 1640 new entries. All are #, fuzzy and none is Review: human.
  • Reviewed entries stay at 85 per locale. None is lost, no reviewed msgstr changed and no entry is removed.
  • release-check passes, with no draft compiled into a pack.
  • The literal baseline goes from 2589 to 1555 and adds nothing compared with main. literal-allowlist.json is unchanged.

(a) Core change (published package), compared with @pascal-app/core@1.0.6

  • The change only adds things: floorFootprintNaming, type FloorFootprintNaming and formatList.
  • floorFootprintName keeps its signature ((nodes, plate) => string) and every export stays.
  • I compared head's floorFootprintName with the published 1.0.6 build on 5998 random plate and room scenes, using legacy generated names, names ending in "floor" and padded names. The output was identical every time.
  • So MCP and agent English is unchanged. packages/mcp has no diff and passes 1331 tests; core passes 6125.
  • No schema, migration or stored-name change in core. Localized footprint names are only displayed.

(b) My P2s from #1172 and #1173

  • hasNodeDefinitions skips only under I18N_ROOT, the test's temporary catalog projects. Anywhere else, a missing nodes entry throws. A test covers both paths.
  • The × stays; the code lives in [Maintainer] i18n: migrate left rail, level selector and layers panel #1173 and this PR doesn't touch it. The PR body links the screenshots, but they are Cursor artifact links I couldn't open.

(c) Lingui migration and the audit are real

  • i18n:check passes: 2367 drafts per locale and 1555 candidates. i18n:literals check passes.
  • With TZ=UTC, all nine inspector surfaces pass the pseudo-audit.
  • To confirm the audit catches regressions, I reverted two labels to plain strings: room-panel "Area" and wall-panel "Dimensions", with nodes rebuilt. Both surfaces failed, and the literal check flagged the change too.

(d) Behaviour and tests

  • Tests: packages/editor 2671 pass, packages/nodes 5022 pass, scripts 130 pass, all with 0 failures.
  • The changed getNodeDisplay, getTypeDisplay and separateFloorSummary signatures are used only inside the editor and aren't exported.

(e) Depends on #1173: rebase needed after it merges

P2 (don't block)

  1. The pseudo-audit depends on time zone. Units come from the time zone, so in an imperial zone (America/New_York) room-panel fails on "430.56ft²"; only TZ=UTC passes. Pin the audit browser to metric, or to a timezoneId, so results don't depend on the machine.
  2. Room-name suggestions are stored in the UI language. Once the drafts are reviewed, a French user picking "Kitchen" stores the French name. MCP will then see that name, and core will build " floor". Numbering won't group an English "Kitchen" with its French name in a mixed-language project. Existing data and the schema are untouched. Please confirm this is intended and document it.
  3. formatList changes English UI wording. In English the shared-walls note now reads "Kitchen and Hall" where it used to read "Kitchen, Hall". The JSDoc example still shows "Kitchen, Hall".
  4. Small mismatch in the PR body. It says "5,023 pass" for nodes; I get 5022 with bun test.

Split recommendation. The code outside the catalogs is about 4.4k insertions and 2.5k deletions across 66 files, and it has no Bugbot review. The catalogs are mechanical and checked by script, so they don't hide risk. The code is where an automated second pass is missing. For the next inspector PR (about 580 strings), split it into the core and script changes, the editor panels, and the node inspectors, each with its own extraction, so Bugbot can review them. I don't recommend re-splitting this one now that it has been reviewed by hand.

Security isn't needed: no auth, secrets, schema or migration changes. See P2 2 on stored room names.

…language

Codex P1: picking a translated suggestion saved the translation (French
"Chambre", unnumbered next to an existing Bedroom). Sections keep English
names with translated labels; the combobox shows and searches labels, a pick
stores pickedRoomName (English, numbered), typed text stays as written.
displayRoomName shows catalog names translated with their number
("Bedroom 2" → "Chambre 2") without touching stored data.
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.
…named

displayRoomName is applied at the display boundary only: the room panel title,
field and mezzanine kicker, the layers rows (as display names; editing still
edits the stored name), unit and zone lists, 2D/3D room pills, viewer chrome
(breadcrumb, sidebar, hover), the walkthrough HUD, node display, footprint
names, the floor-opening kicker, shared-wall notes, unit members and zone
quantities. Unnamed openings read "in Room" again (Codex nit). Floor-plan
builders, exports and agent context keep the stored English.
…s (Codex P2s)

- Window-merge refusals select on the blocking node's type with one sentence
  per kind; English reads "There is an item between them." again.
- Run lengths, hole areas, curtain-wall spacing and effective thickness use
  formatNumber with the UI locale instead of toFixed.
- Room opening counts are one ICU plural, keeping "No openings".
- "1 custom panel is outside this grid. It returns…" (nit).
English stays "Kitchen, Hall" as before; other locales use Intl.ListFormat
for their own separators. The doc comment says so, with a test.
…tric everywhere

The editor picks imperial from an American timezone; run from New York the
audit saw 430.56ft² where the surfaces list metric data.
…ey lookups

Editing a translated name (with or without a number) maps back to the
English catalog name and skips the write when unchanged. Filtering also
matches English names, an exact match is the active row, and the typed
row is always offered. Prototype keys like constructor are free text.
Adds a French-UI test that every room-name write path stores the same
English as main.
Drops the reverse lookup across all translations. An edit of the room
panel's field stores English only when the room's stored name is a catalog
name and the edit is that name's translated label with the number changed,
added or removed (in French, 'Salon 3' on a Lounge stores 'Lounge 3').
Everything else, a click on 'Use "…"' and an exact translation such as
'Cuisine' included, is stored exactly as typed, surrounding spaces too.
The 'Use' row is hidden again for an exact English name, as on main, and
Enter on it commits like blur. The storage test drives the panel's commit
and pick functions.

Copy link
Copy Markdown
Member Author

Tooling cross-review at b563163: APPROVE

I reviewed this from scratch at this SHA (main 23bdaf4 merged in). I looked at the code outside the catalogs and checked the catalogs by script.

P1: none. P2: none.

What I verified

  • Room-name design (966702a). editedRoomName only converts when the stored base is an own key of ROOM_NAME_MESSAGES and the edited base equals that entry's label. In that case it keeps the edit's lead, number and trail. Anything else is stored as typed, and there's no reverse lookup left. RoomName sends blur, Enter on the typed row (through blur), suggestion click, Enter on a suggestion and the Use "…" click through commitRoomNameEdit/commitRoomNamePick only, with no inline renameRoom left. The other name writes are unchanged from main, and none of them stores localized text: creation, duplicate, paste, MCP set_zone_intent, undo, the layer-tree InlineRenameInput (stored text, trimmed as on main) and the unit name field. No core schema or store change, no migration, and packages/mcp is untouched.
  • Storage test. room-name-storage.test.ts passes 12/12. I swapped in 585db60's storedRoomName/pickedRoomName with its inline commit logic, and 4 tests fail: Lounge→'Salon 3', free-text 'Salon 2', the Use "Chambre" click (which stored 'Bedroom') and spaces.
  • My own edge cases (French, Lounge/Living room both 'Salon', Office/Study both 'Bureau'):
    • Converted to the room's own entry: Lounge+'Salon 2'→'Lounge 2', 'Lounge 2'+'Salon'→'Lounge', 'Salon 03'→'Lounge 03', 'Living room 1'+'Salon 2'→'Living room 2', 'Study'+'Bureau 2'→'Study 2'.
    • Stored as typed: 'Salon Feat/cc and levels #3', 'Salon III', 'Salon3', a full-width digit, 'salon 3', 'Chambre 3' on a Lounge, '' and free-text 'Salon 2'→'Salon 3'. constructor, __proto__, toString, hasOwnProperty and 'valueOf 3' stay free text and display as written. In an English UI every edit is identity, the same as main.
  • No rewrite of existing data. I loaded main's two-bedroom scene with names 'Lounge 2', ' Kitchen ', 'Salon' and 'constructor' in a French UI, blurred each name field unchanged and changed every zone's colour. The saved document deep-equals the original except for the colour, and get_zones returns the stored names.
  • Viewer and number formatting. The viewer labels take the translator as a parameter (zoneLabel(zone, i18n)), and packages/viewer imports nothing from the editor. formatFixed: -0.001→'0.00', 2.55→'2.5' (same as toFixed), 12345.678→'12345.7' with no grouping. formatList gives 'Kitchen, Hall' in en and en-GB.
  • Catalogs. In es, fr, ja and zh-CN, all 1,644 new entries are fuzzy, none is empty, reviewed stays 85→85, no reviewed msgstr changed and none was lost. The compiled packs hold exactly the 85 reviewed ids, so no drafts leak. i18n:check passes (I18N_BASE_REF=23bdaf47). release-check passes against the default base (origin/main, since the 1.0.6 tag has no catalogs) and against --base 23bdaf47. The literal baseline only loses entries (-1120/+0), and the allowlist, scripts/i18n.ts and the workflows are unchanged. hasNodeDefinitions now throws outside temp projects, which tightens that check.
  • Core compatibility vs published 1.0.6. The root index removes no exports (25 added). floorFootprintName gives identical output to 1.0.6 on 7,926 randomized plates covering named, room, shared, area and keyed-creator cases.
  • Tests and CI. core 6162/0, editor 2700/0, nodes 5025/0, mcp 1338/0 (nodes and mcp run through turbo with deps built), scripts i18n 56/0. Required CI (ci, quality, cli-smoke, Changed paths) is green at the head, and the PR is mergeable clean.

On the three accepted choices

  • (a) Correct. On main, Enter on the typed row picked the typed row, which stored option.value (the raw text), and blur stored the same raw text. So in English, "Enter commits like blur" stores exactly what main stored. In French it can't bypass the conversion, and only the explicit Use click stores text literally.
  • (b) Correct. The highlighted row is options[active] and Enter picks options[active], so Enter can never pick a different row than the highlighted one. A shared label falls back to main's first-suggestion rule.
  • (c) Correct. The test imports the same commitRoomNameEdit/commitRoomNamePick the component calls, with the same committed = display contract. The Enter routing itself (suggestion → pick, otherwise blur) has no test, but it's three lines and reads correctly.

P3 (non-blocking follow-ups)

  1. Shared-label Enter. With both 'Salon' rows showing, typing 'Salon' on a Lounge and pressing Enter picks Living room (the first suggestion). That's consistent with the highlight and stores English, but the two rows look identical. Consider breaking the tie in favour of the room's current entry, and add a roomNameOptions test for the shared-label active (none exists). Drafts don't ship, so this only matters once a reviewer accepts duplicate labels.
  2. English Enter change from main. An exact English match now wins: 'Closet'/'closet' + Enter picks Closet, where main picked 'Walk-in closet', the first containing name. It's arguably a fix, but worth a line in the PR body.
  3. Grouped # counts. Some added plurals still use #, which Lingui groups: '# pt(s)', '# wall(s)', '# run(s)', '# row(s)', '# open side(s)', 'Lower/Raise all #'. They render '1,234' where main wrote '1234'. That only matters at 1000 or more, so the {number} pattern from fd8d820 could be applied in a follow-up.
  4. Layer-tree rename. In a French UI, the inline rename in the layer tree shows the stored English while editing. That's the same as main and it stores as typed, so it's fine for the bar, but it's a small UX mismatch with the panel.

Not verified locally: the en-XA pseudo-audit (TZ=America/New_York). The furnished audit scene never finished loading in headless Chrome on my box, so room-panel timed out waiting for 'Living / Kitchen'. The navbar, navbar-mobile, build-tab and tool-dock surfaces passed. The other main surfaces that loaded reported 1 untranslated string each and home/settings reported 92 and 21 (it's a contributor tool, not a CI gate), and mep-tools also timed out. The PR's own screenshots stand for those.

Security: not needed. No path stores localized text, and no existing scene data is rewritten.

@Aymericr
Aymericr merged commit e123cf6 into main Oct 27, 2026
4 checks passed
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