@pascal-app/editor 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/package.json +18 -15
  2. package/src/components/editor/action-menu-button.tsx +4 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
  4. package/src/components/editor/bake-thumbnail.tsx +3 -2
  5. package/src/components/editor/custom-camera-controls.tsx +20 -8
  6. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  7. package/src/components/editor/first-person-controls.tsx +72 -61
  8. package/src/components/editor/floating-action-menu.tsx +27 -9
  9. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  10. package/src/components/editor/floorplan-panel.test.ts +220 -0
  11. package/src/components/editor/floorplan-panel.tsx +285 -132
  12. package/src/components/editor/footprint-height-handle.tsx +1 -1
  13. package/src/components/editor/grid.tsx +24 -15
  14. package/src/components/editor/group-actions.ts +19 -1
  15. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  16. package/src/components/editor/group-move-3d.ts +27 -2
  17. package/src/components/editor/group-selection-box-3d.tsx +4 -1
  18. package/src/components/editor/group-transform-shared.ts +20 -0
  19. package/src/components/editor/index.tsx +130 -20
  20. package/src/components/editor/inspector-extension-actions.tsx +36 -0
  21. package/src/components/editor/localized-html.tsx +22 -0
  22. package/src/components/editor/node-action-menu.tsx +46 -12
  23. package/src/components/editor/node-arrow-handles.tsx +1 -2
  24. package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
  25. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  26. package/src/components/editor/registry-action-contributions.test.tsx +67 -6
  27. package/src/components/editor/registry-action-contributions.tsx +115 -15
  28. package/src/components/editor/riser-diagram-panel.tsx +59 -9
  29. package/src/components/editor/room-controls.tsx +1 -1
  30. package/src/components/editor/room-handles-2d.tsx +156 -6
  31. package/src/components/editor/room-handles.tsx +241 -51
  32. package/src/components/editor/room-highlight.tsx +2 -0
  33. package/src/components/editor/room-labels.tsx +29 -15
  34. package/src/components/editor/selection-manager.tsx +15 -102
  35. package/src/components/editor/separator-end-grip.tsx +74 -0
  36. package/src/components/editor/site-edge-labels.tsx +1 -1
  37. package/src/components/editor/thumbnail-generator.test.ts +15 -6
  38. package/src/components/editor/thumbnail-generator.tsx +26 -1
  39. package/src/components/editor/use-floorplan-background-placement.ts +55 -52
  40. package/src/components/editor/views/view-bar.tsx +111 -26
  41. package/src/components/editor/wall-measurement-label.tsx +1 -1
  42. package/src/components/editor/wall-move-side-handles.tsx +5 -1
  43. package/src/components/editor/wall-pull-grip.tsx +193 -0
  44. package/src/components/editor/wall-snap-beacon-layer.tsx +81 -1
  45. package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
  46. package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
  47. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
  48. package/src/components/editor-2d/floorplan-group-move.tsx +31 -3
  49. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
  50. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +22 -13
  51. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +11 -3
  52. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +7 -1
  53. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
  54. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
  55. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +12 -1
  56. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +197 -37
  57. package/src/components/procedural-items/inspector.tsx +59 -12
  58. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
  59. package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
  60. package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
  61. package/src/components/tools/distribution/distribution-run-tool.tsx +50 -26
  62. package/src/components/tools/item/placement-history.test.tsx +1 -1
  63. package/src/components/tools/item/placement-strategies.test.ts +64 -1
  64. package/src/components/tools/item/placement-strategies.ts +5 -9
  65. package/src/components/tools/item/use-placement-coordinator.tsx +30 -34
  66. package/src/components/tools/registry/item-surface-move.test.ts +11 -0
  67. package/src/components/tools/select/select-candidates.test.ts +45 -0
  68. package/src/components/tools/select/select-candidates.ts +13 -2
  69. package/src/components/tools/shared/cursor-sphere.tsx +10 -3
  70. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  71. package/src/components/tools/shared/placement-box.tsx +1 -1
  72. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  73. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  74. package/src/components/tools/wall/wall-drafting.test.ts +299 -23
  75. package/src/components/tools/wall/wall-drafting.ts +132 -24
  76. package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
  77. package/src/components/tools/wall/wall-pull-snap.ts +563 -0
  78. package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
  79. package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
  80. package/src/components/tools/zone/zone-tool.tsx +3 -3
  81. package/src/components/ui/action-menu/action-button.tsx +2 -0
  82. package/src/components/ui/action-menu/camera-actions.tsx +8 -7
  83. package/src/components/ui/action-menu/control-modes.tsx +9 -5
  84. package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
  85. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
  86. package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
  87. package/src/components/ui/action-menu/index.tsx +1 -0
  88. package/src/components/ui/action-menu/measurement-control.tsx +35 -29
  89. package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
  90. package/src/components/ui/action-menu/structure-tools.tsx +25 -23
  91. package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
  92. package/src/components/ui/action-menu/view-toggles.tsx +256 -51
  93. package/src/components/ui/build-panel/build-panel.tsx +163 -25
  94. package/src/components/ui/command-palette/editor-commands.tsx +38 -11
  95. package/src/components/ui/controls/finish-swatch.tsx +45 -19
  96. package/src/components/ui/controls/material-picker.tsx +24 -17
  97. package/src/components/ui/controls/metric-control.test.ts +52 -0
  98. package/src/components/ui/controls/metric-control.tsx +62 -24
  99. package/src/components/ui/controls/segmented-control.tsx +5 -2
  100. package/src/components/ui/controls/slider-control.test.tsx +23 -0
  101. package/src/components/ui/controls/slider-control.tsx +30 -14
  102. package/src/components/ui/floating-level-selector.tsx +100 -48
  103. package/src/components/ui/helpers/building-helper.tsx +5 -3
  104. package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
  105. package/src/components/ui/helpers/helper-manager.test.tsx +53 -3
  106. package/src/components/ui/helpers/helper-manager.tsx +82 -57
  107. package/src/components/ui/helpers/item-helper.tsx +6 -4
  108. package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
  109. package/src/components/ui/level-duplicate-dialog.tsx +32 -23
  110. package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
  111. package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
  112. package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
  113. package/src/components/ui/panels/floor-foundation-panel.tsx +113 -38
  114. package/src/components/ui/panels/floor-opening-panel.tsx +73 -25
  115. package/src/components/ui/panels/inspector-extensions.tsx +66 -0
  116. package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
  117. package/src/components/ui/panels/mobile-selection-bar.tsx +8 -6
  118. package/src/components/ui/panels/multi-field-value.test.ts +27 -0
  119. package/src/components/ui/panels/multi-field-value.ts +20 -1
  120. package/src/components/ui/panels/multi-height-mode.tsx +8 -7
  121. package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
  122. package/src/components/ui/panels/multi-selection-panel.tsx +75 -35
  123. package/src/components/ui/panels/node-display.ts +35 -24
  124. package/src/components/ui/panels/panel-manager.tsx +5 -3
  125. package/src/components/ui/panels/panel-wrapper.tsx +22 -109
  126. package/src/components/ui/panels/parametric-field-control.tsx +13 -10
  127. package/src/components/ui/panels/parametric-inspector.tsx +18 -12
  128. package/src/components/ui/panels/reference-panel.tsx +51 -35
  129. package/src/components/ui/panels/room-construction-rows.tsx +464 -65
  130. package/src/components/ui/panels/room-name-options.test.ts +36 -3
  131. package/src/components/ui/panels/room-name-options.ts +110 -11
  132. package/src/components/ui/panels/room-openings-field.tsx +21 -12
  133. package/src/components/ui/panels/room-panel.tsx +206 -99
  134. package/src/components/ui/panels/shape-choice.tsx +7 -5
  135. package/src/components/ui/panels/wall-region-list.tsx +7 -7
  136. package/src/components/ui/primitives/shortcut-token.tsx +10 -6
  137. package/src/components/ui/sidebar/icon-rail.tsx +11 -9
  138. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
  139. package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
  140. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
  141. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
  142. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
  143. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -2
  144. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
  145. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
  146. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
  147. package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
  148. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
  149. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
  150. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
  151. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
  152. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
  153. package/src/components/ui/sidebar/panels/site-panel/index.tsx +117 -64
  154. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
  155. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
  156. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
  157. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
  158. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
  159. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
  160. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +8 -3
  161. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
  162. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
  163. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
  164. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
  165. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
  166. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
  167. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
  168. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
  169. package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
  170. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +18 -8
  171. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
  172. package/src/components/ui/sidebar/tab-bar.tsx +4 -1
  173. package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
  174. package/src/components/ui/snap-target-badge.tsx +7 -6
  175. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
  176. package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
  177. package/src/components/viewer/floorplan-compass-button.tsx +12 -3
  178. package/src/components/viewer/floorplan-preview.tsx +27 -19
  179. package/src/components/viewer/render-settings-menu.tsx +294 -0
  180. package/src/components/viewer/sky-control.tsx +710 -0
  181. package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
  182. package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
  183. package/src/components/viewer/viewer-controls-bar.tsx +278 -260
  184. package/src/components/viewer/viewer-hover-label.tsx +12 -4
  185. package/src/components/viewer/viewer-rooms.tsx +61 -4
  186. package/src/components/viewer/viewer-scene-header.tsx +28 -17
  187. package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
  188. package/src/components/viewer-overlay.tsx +8 -0
  189. package/src/components/walkthrough-hud.tsx +75 -2
  190. package/src/hooks/use-keyboard.test.ts +85 -4
  191. package/src/hooks/use-keyboard.ts +65 -7
  192. package/src/hooks/use-selected-room.ts +12 -1
  193. package/src/i18n/floor-footprint-name.ts +30 -0
  194. package/src/i18n/level-name.ts +39 -0
  195. package/src/i18n/locales/en/messages.po +15117 -0
  196. package/src/i18n/locales/en/messages.ts +1 -0
  197. package/src/i18n/locales/en-XA/messages.ts +1 -0
  198. package/src/i18n/locales/es/messages.po +37501 -0
  199. package/src/i18n/locales/es/messages.ts +1 -0
  200. package/src/i18n/locales/fr/messages.po +37501 -0
  201. package/src/i18n/locales/fr/messages.ts +1 -0
  202. package/src/i18n/locales/ja/messages.po +37501 -0
  203. package/src/i18n/locales/ja/messages.ts +1 -0
  204. package/src/i18n/locales/zh-CN/messages.po +37501 -0
  205. package/src/i18n/locales/zh-CN/messages.ts +1 -0
  206. package/src/i18n/locales.test.ts +63 -0
  207. package/src/i18n/locales.ts +32 -0
  208. package/src/i18n/message-slot.tsx +25 -0
  209. package/src/i18n/node-messages.ts +169 -0
  210. package/src/i18n/pseudo-locale.ts +17 -0
  211. package/src/i18n/published-entry.test.ts +17 -0
  212. package/src/i18n/scene-theme-name.ts +16 -0
  213. package/src/index.tsx +156 -4
  214. package/src/lib/add-storey.test.ts +3 -3
  215. package/src/lib/contextual-help.test.ts +20 -1
  216. package/src/lib/contextual-help.ts +95 -19
  217. package/src/lib/continuation.ts +38 -11
  218. package/src/lib/floor-footprints.test.ts +2 -1
  219. package/src/lib/floor-foundation-support.test.ts +7 -3
  220. package/src/lib/floorplan/dimension-drive.test.ts +36 -2
  221. package/src/lib/floorplan/dimension-drive.ts +68 -3
  222. package/src/lib/floorplan/floorplan-export.test.ts +16 -5
  223. package/src/lib/floorplan/floorplan-export.tsx +13 -16
  224. package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
  225. package/src/lib/floorplan/floorplan-extension.ts +37 -18
  226. package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
  227. package/src/lib/floorplan/floorplan-mode.ts +14 -12
  228. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
  229. package/src/lib/floorplan/floorplan-readonly.ts +3 -3
  230. package/src/lib/floorplan/schedules.ts +10 -6
  231. package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
  232. package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
  233. package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
  234. package/src/lib/geometry-script/author.test.ts +114 -0
  235. package/src/lib/geometry-script/author.ts +55 -23
  236. package/src/lib/geometry-script/client.ts +3 -0
  237. package/src/lib/geometry-script/finish-scripts.ts +7 -7
  238. package/src/lib/glb-export.test.ts +327 -0
  239. package/src/lib/glb-export.ts +70 -2
  240. package/src/lib/grid-frame.test.ts +56 -3
  241. package/src/lib/grid-frame.ts +28 -1
  242. package/src/lib/history.test.ts +2 -1
  243. package/src/lib/hud-title.ts +48 -24
  244. package/src/lib/ifc-export.test.ts +41 -1
  245. package/src/lib/inspector-card-mode.test.ts +3 -31
  246. package/src/lib/inspector-card-mode.ts +5 -10
  247. package/src/lib/inspector-expanded.test.ts +13 -0
  248. package/src/lib/inspector-expanded.ts +13 -1
  249. package/src/lib/keyboard-pan.test.ts +7 -0
  250. package/src/lib/keyboard-pan.ts +9 -2
  251. package/src/lib/layer-thickness.test.ts +22 -0
  252. package/src/lib/layer-thickness.ts +79 -0
  253. package/src/lib/lot/drop-in.test.ts +59 -1
  254. package/src/lib/lot/drop-in.ts +42 -50
  255. package/src/lib/material-swatch-style.ts +24 -0
  256. package/src/lib/measurement-parser.ts +7 -3
  257. package/src/lib/measurements.test.ts +17 -0
  258. package/src/lib/measurements.ts +20 -5
  259. package/src/lib/mezzanine-selection.test.ts +3 -1
  260. package/src/lib/orbit-near-plane.test.ts +12 -0
  261. package/src/lib/orbit-near-plane.ts +13 -0
  262. package/src/lib/paint-regions.test.ts +8 -5
  263. package/src/lib/paint-scope.test.ts +53 -0
  264. package/src/lib/paint-scope.ts +18 -4
  265. package/src/lib/panel-rows.ts +17 -8
  266. package/src/lib/reference-scale.ts +83 -0
  267. package/src/lib/room-built-on.test.ts +26 -12
  268. package/src/lib/room-built-on.ts +20 -2
  269. package/src/lib/room-construction.test.ts +28 -1
  270. package/src/lib/room-construction.ts +14 -0
  271. package/src/lib/room-controls-phase6.test.ts +140 -0
  272. package/src/lib/room-handle-drag.ts +557 -42
  273. package/src/lib/room-name-catalog.test.ts +8 -0
  274. package/src/lib/room-name-catalog.ts +8 -1
  275. package/src/lib/room-name-messages.test.ts +132 -0
  276. package/src/lib/room-name-messages.ts +147 -0
  277. package/src/lib/room-name-storage.test.ts +334 -0
  278. package/src/lib/room-push-dimensions.test.ts +37 -0
  279. package/src/lib/room-push-dimensions.ts +226 -2
  280. package/src/lib/room-selection.test.ts +23 -6
  281. package/src/lib/scene-clipboard.test.ts +70 -49
  282. package/src/lib/scene-clipboard.ts +9 -4
  283. package/src/lib/selection-cycle.ts +63 -0
  284. package/src/lib/separator-edit.test.ts +431 -0
  285. package/src/lib/separator-edit.ts +179 -0
  286. package/src/lib/separator-lines.ts +38 -0
  287. package/src/lib/sfx-bus.ts +27 -1
  288. package/src/lib/sfx-player.ts +16 -0
  289. package/src/lib/tool-cursor-icon.ts +5 -1
  290. package/src/lib/tool-shortcuts.ts +130 -0
  291. package/src/lib/viewer-selection.test.ts +28 -24
  292. package/src/lib/viewer-selection.ts +20 -5
  293. package/src/lib/wall-draw-variant.test.ts +7 -6
  294. package/src/lib/wall-draw-variant.ts +107 -6
  295. package/src/lib/wall-nudge.test.ts +44 -0
  296. package/src/lib/wall-nudge.ts +131 -0
  297. package/src/lib/wall-parallel-snap.test.ts +83 -0
  298. package/src/lib/wall-parallel-snap.ts +176 -0
  299. package/src/lib/wall-placement-pointer.test.ts +23 -0
  300. package/src/lib/wall-placement-pointer.ts +18 -0
  301. package/src/lib/wall-pull.test.ts +245 -0
  302. package/src/lib/wall-pull.ts +209 -0
  303. package/src/lib/wall-push-line-up.test.ts +132 -0
  304. package/src/lib/wall-region-list.ts +14 -5
  305. package/src/lib/wall-run-snap.test.ts +188 -0
  306. package/src/lib/wall-run-snap.ts +83 -0
  307. package/src/lib/wall-type.test.ts +125 -0
  308. package/src/lib/wall-type.ts +119 -0
  309. package/src/lib/window-interaction.test.ts +42 -0
  310. package/src/lib/window-interaction.ts +47 -5
  311. package/src/lib/world-grid-snap.test.ts +139 -2
  312. package/src/lib/world-grid-snap.ts +102 -105
  313. package/src/store/use-alignment-guides.ts +27 -1
  314. package/src/store/use-editor.tsx +48 -0
  315. package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
  316. package/src/store/use-floorplan-annotation-visibility.ts +47 -22
  317. package/src/store/use-wall-snap-indicator.ts +15 -2
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import type { AnyNodeId, CollectionId } from '@pascal-app/core'
4
- import { useScene } from '@pascal-app/core'
4
+ import { useScene, usePascalI18n } from '@pascal-app/core'
5
5
  import {
6
6
  Check,
7
7
  ChevronDown,
@@ -36,6 +36,7 @@ interface CollectionsPopoverProps {
36
36
  }
37
37
 
38
38
  export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProps) {
39
+ const i18n = usePascalI18n()
39
40
  const collections = useScene((s) => s.collections)
40
41
  const nodes = useScene((s) => s.nodes)
41
42
  const createCollection = useScene((s) => s.createCollection)
@@ -102,7 +103,7 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
102
103
  <div className="flex items-center gap-1.5">
103
104
  <Layers className="h-3.5 w-3.5 text-muted-foreground" />
104
105
  <span className="font-semibold text-foreground text-xs tracking-tight">
105
- Collections
106
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.collections', message: 'Collections' })}
106
107
  </span>
107
108
  </div>
108
109
  <button
@@ -114,7 +115,7 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
114
115
  type="button"
115
116
  >
116
117
  <Plus className="h-3 w-3" />
117
- New
118
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.new', message: 'New' })}
118
119
  </button>
119
120
  </div>
120
121
 
@@ -132,7 +133,7 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
132
133
  setCreateName('')
133
134
  }
134
135
  }}
135
- placeholder="Collection name…"
136
+ placeholder={i18n._({ id: 'editor.panels.collections.collectionsPopover.collectionName', message: 'Collection name…' })}
136
137
  value={createName}
137
138
  />
138
139
  <button
@@ -162,7 +163,7 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
162
163
  <div className="flex flex-col items-center justify-center gap-2 px-4 py-8 text-center">
163
164
  <Layers className="h-6 w-6 text-muted-foreground/40" />
164
165
  <p className="text-muted-foreground text-xs">
165
- No collections yet. Create one to group elements together.
166
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.noCollectionsYetCreateOneTo', message: 'No collections yet. Create one to group elements together.' })}
166
167
  </p>
167
168
  </div>
168
169
  ) : (
@@ -182,8 +183,7 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
182
183
  key={collection.id}
183
184
  >
184
185
  <span className="truncate text-foreground/80 text-xs">
185
- Delete "{collection.name}"?
186
- </span>
186
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.deleteName', message: 'Delete "{name}"?', values: { name: collection.name } })}</span>
187
187
  <div className="flex shrink-0 items-center gap-1">
188
188
  <button
189
189
  className="rounded-md bg-red-500/20 px-2 py-0.5 font-medium text-[11px] text-red-600 dark:text-red-400 transition-colors hover:bg-red-500/30"
@@ -193,14 +193,14 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
193
193
  }}
194
194
  type="button"
195
195
  >
196
- Delete
196
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.delete', message: 'Delete' })}
197
197
  </button>
198
198
  <button
199
199
  className="rounded-md px-2 py-0.5 font-medium text-[11px] text-muted-foreground transition-colors hover:bg-black/6 dark:hover:bg-white/10"
200
200
  onClick={() => setDeletingId(null)}
201
201
  type="button"
202
202
  >
203
- Cancel
203
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.cancel', message: 'Cancel' })}
204
204
  </button>
205
205
  </div>
206
206
  </li>
@@ -314,14 +314,14 @@ export function CollectionsPopover({ nodeIds, children }: CollectionsPopoverProp
314
314
  }}
315
315
  >
316
316
  <Pencil className="h-3.5 w-3.5" />
317
- Rename
317
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.rename', message: 'Rename' })}
318
318
  </DropdownMenuItem>
319
319
  <DropdownMenuItem
320
320
  onClick={() => setDeletingId(collection.id)}
321
321
  variant="destructive"
322
322
  >
323
323
  <Trash2 className="h-3.5 w-3.5" />
324
- Delete
324
+ {i18n._({ id: 'editor.panels.collections.collectionsPopover.delete', message: 'Delete' })}
325
325
  </DropdownMenuItem>
326
326
  </DropdownMenuContent>
327
327
  </DropdownMenu>
@@ -0,0 +1,136 @@
1
+ 'use client'
2
+
3
+ import {
4
+ type AnyNodeId,
5
+ DECK_BOARD_GAPS,
6
+ DECK_BOARD_SIZES,
7
+ DECK_BOARD_STOCK,
8
+ type DeckBoardDirection,
9
+ type DeckBoardSize,
10
+ deckBoardSettings,
11
+ deckJoistSpacingLimit,
12
+ inchFractionLabel,
13
+ type MessageDescriptor,
14
+ setZoneIntent,
15
+ usePascalI18n,
16
+ useScene,
17
+ } from '@pascal-app/core'
18
+ import type { ReactNode } from 'react'
19
+ import { useShallow } from 'zustand/react/shallow'
20
+ import { applyRoomPlan } from '../../../lib/room-structure-commands'
21
+ import { SegmentedControl } from '../controls/segmented-control'
22
+
23
+ const IN = 0.0254
24
+
25
+ const BOARD_OPTIONS = DECK_BOARD_SIZES.map((value) => ({
26
+ label: DECK_BOARD_STOCK[value].label,
27
+ value,
28
+ }))
29
+
30
+ const GAP_OPTIONS = DECK_BOARD_GAPS.map((gap, index) => ({
31
+ label: `${inchFractionLabel(gap)} in`,
32
+ value: String(index),
33
+ }))
34
+
35
+ const RUN_OPTIONS: ReadonlyArray<{ label: MessageDescriptor; value: DeckBoardDirection }> = [
36
+ {
37
+ label: /* i18n */ {
38
+ id: 'editor.panels.deckBoardsFields.alongHouse',
39
+ message: 'Along house',
40
+ },
41
+ value: 'parallel',
42
+ },
43
+ {
44
+ label: /* i18n */ { id: 'editor.panels.deckBoardsFields.diagonal', message: 'Diagonal' },
45
+ value: 'diagonal',
46
+ },
47
+ {
48
+ label: /* i18n */ {
49
+ id: 'editor.panels.deckBoardsFields.outFromHouse',
50
+ message: 'Out from house',
51
+ },
52
+ value: 'perpendicular',
53
+ },
54
+ ]
55
+
56
+ function Field({ label, children }: { label: string; children: ReactNode }) {
57
+ return (
58
+ <div className="flex min-h-7 items-center justify-between gap-3">
59
+ <span className="shrink-0">{label}</span>
60
+ {children}
61
+ </div>
62
+ )
63
+ }
64
+
65
+ /**
66
+ * A deck's boards (`decking`, through core's `setZoneIntent`): their stock, the gap between them
67
+ * and the way they run against the house. The engine lays them over the deck's frame, and the
68
+ * frame follows: joists 12 in on centre under diagonal 5/4 or composite boards (IRC R507.7), and
69
+ * along the house on beams square to it under boards run out from it.
70
+ */
71
+ export function DeckBoardsFields({ zoneId }: { zoneId: string }) {
72
+ const i18n = usePascalI18n()
73
+ const deck = useScene(
74
+ useShallow((scene) => {
75
+ const zone = scene.nodes[zoneId as AnyNodeId]
76
+ if (zone?.type !== 'zone' || zone.roomType !== 'deck') return null
77
+ return deckBoardSettings(zone)
78
+ }),
79
+ )
80
+ if (!deck) return null
81
+ const set = (decking: NonNullable<Parameters<typeof setZoneIntent>[1]['patch']['decking']>) =>
82
+ applyRoomPlan(setZoneIntent(useScene.getState().nodes, { zoneId, patch: { decking } }))
83
+ const stock = DECK_BOARD_STOCK[deck.boardSize]
84
+ const gap = DECK_BOARD_GAPS.findIndex((option) => Math.abs(option - deck.gap) < 1e-6)
85
+ const spacing = Math.min(16 * IN, deckJoistSpacingLimit(deck))
86
+ return (
87
+ <div
88
+ className="mt-3 flex flex-col gap-1.5 text-muted-foreground text-xs"
89
+ data-deck-boards={deck.boardSize}
90
+ >
91
+ <h3 className="font-medium text-xs">
92
+ {i18n._({ id: 'editor.panels.deckBoardsFields.deckBoards', message: 'Deck boards' })}
93
+ </h3>
94
+ <Field label={i18n._({ id: 'editor.panels.deckBoardsFields.boards', message: 'Boards' })}>
95
+ <SegmentedControl
96
+ onChange={(boardSize: DeckBoardSize) => set({ boardSize })}
97
+ options={BOARD_OPTIONS}
98
+ value={deck.boardSize}
99
+ />
100
+ </Field>
101
+ <Field label={i18n._({ id: 'editor.panels.deckBoardsFields.gap', message: 'Gap' })}>
102
+ <SegmentedControl
103
+ mixed={gap < 0}
104
+ onChange={(index: string) => set({ gap: DECK_BOARD_GAPS[Number(index)] })}
105
+ options={GAP_OPTIONS}
106
+ value={String(Math.max(gap, 0))}
107
+ />
108
+ </Field>
109
+ <Field label={i18n._({ id: 'editor.panels.deckBoardsFields.run', message: 'Run' })}>
110
+ <SegmentedControl
111
+ onChange={(direction: DeckBoardDirection) => set({ direction })}
112
+ options={RUN_OPTIONS.map((option) => ({
113
+ label: i18n._(option.label),
114
+ value: option.value,
115
+ }))}
116
+ value={deck.direction}
117
+ />
118
+ </Field>
119
+ <p>
120
+ {i18n._({
121
+ id: 'editor.panels.deckBoardsFields.boardsSummary',
122
+ message:
123
+ '{label} boards {thickness} × {width} in, {gap} in apart, {direction, select, parallel {along the house, on joists square to it} diagonal {at 45° to the house} other {out from the house, on joists along it, carried by beams square to the house}}; joists {spacing} in on centre.',
124
+ values: {
125
+ label: stock.label,
126
+ thickness: inchFractionLabel(stock.thickness),
127
+ width: inchFractionLabel(stock.width),
128
+ gap: inchFractionLabel(deck.gap),
129
+ direction: deck.direction,
130
+ spacing: Math.round(spacing / IN),
131
+ },
132
+ })}
133
+ </p>
134
+ </div>
135
+ )
136
+ }
@@ -4,19 +4,22 @@ import {
4
4
  type AnyNode,
5
5
  type AnyNodeId,
6
6
  type FloorFoundationPatch,
7
- floorFootprintName,
8
7
  floorFootprintSupportClass,
9
8
  upperFloorHeightControl,
10
- getLevelDisplayName,
11
9
  getLevelElevations,
12
10
  getStoredLevelHeight,
13
11
  type LevelNode,
14
12
  MIN_GROUND_FLOOR_THICKNESS,
15
13
  MIN_SLAB_THICKNESS,
14
+ type PascalI18n,
16
15
  type SlabNode,
16
+ useLocale,
17
+ usePascalI18n,
17
18
  useScene,
18
19
  } from '@pascal-app/core'
19
20
  import { useViewer } from '@pascal-app/viewer'
21
+ import { localizedFloorFootprintName } from '../../../i18n/floor-footprint-name'
22
+ import { localizedLevelName } from '../../../i18n/level-name'
20
23
  import { useRef, useState } from 'react'
21
24
  import {
22
25
  applyFloorFoundation,
@@ -78,6 +81,7 @@ export function FloorSection({
78
81
  thickness: number
79
82
  thicknessLabel: string
80
83
  }) {
84
+ const i18n = usePascalI18n()
81
85
  const base = 100
82
86
  const foundation = !supported && raised ? Math.min(40, 14 + foundationHeight * 30) : 0
83
87
  const band = Math.max(7, Math.min(16, thickness * 60))
@@ -98,32 +102,67 @@ export function FloorSection({
98
102
  const left = 64
99
103
  const right = 164
100
104
  const labels: { y: number; text: string; key: string }[] = [
101
- { key: 'top', y: bandTop - 9, text: 'Floor top' },
105
+ {
106
+ key: 'top',
107
+ y: bandTop - 9,
108
+ text: i18n._({ id: 'editor.panels.floorFoundationPanel.section.floorTop', message: 'Floor top' }),
109
+ },
102
110
  {
103
111
  key: 'plate',
104
112
  y: bandTop + band / 2,
105
- text: supported ? `Floor plate · ${thicknessLabel}` : `Slab · ${thicknessLabel}`,
113
+ text: supported
114
+ ? i18n._({
115
+ id: 'editor.panels.floorFoundationPanel.section.floorPlate',
116
+ message: 'Floor plate · {thickness}',
117
+ values: { thickness: thicknessLabel },
118
+ })
119
+ : i18n._({
120
+ id: 'editor.panels.floorFoundationPanel.section.slab',
121
+ message: 'Slab · {thickness}',
122
+ values: { thickness: thicknessLabel },
123
+ }),
106
124
  },
107
125
  ...(foundation
108
126
  ? [
109
127
  {
110
128
  key: 'foundation',
111
129
  y: bandBottom + foundation / 2,
112
- text: foundationLabel ? `Foundation · ${foundationLabel}` : 'Foundation',
130
+ text: foundationLabel
131
+ ? i18n._({
132
+ id: 'editor.panels.floorFoundationPanel.section.foundationKind',
133
+ message: 'Foundation · {foundation}',
134
+ values: { foundation: foundationLabel },
135
+ })
136
+ : i18n._({ id: 'editor.panels.floorFoundationPanel.section.foundation', message: 'Foundation' }),
113
137
  },
114
138
  ]
115
139
  : []),
116
- { key: 'base', y: base + 9, text: supported ? 'Walls below' : 'Ground' },
140
+ {
141
+ key: 'base',
142
+ y: base + 9,
143
+ text: supported
144
+ ? i18n._({ id: 'editor.panels.floorFoundationPanel.section.wallsBelow', message: 'Walls below' })
145
+ : i18n._({ id: 'editor.panels.floorFoundationPanel.section.ground', message: 'Ground' }),
146
+ },
117
147
  ]
118
148
  return (
119
149
  <figure className="flex flex-col gap-1" data-floor-section>
120
150
  <svg
121
151
  aria-label={
122
152
  supported
123
- ? `Floor plate ${thicknessLabel} thick, resting on the walls below`
153
+ ? i18n._({ id: 'editor.panels.floorFoundationPanel.floorPlateThicknesslabelThickRestingOn', message: 'Floor plate {thicknessLabel} thick, resting on the walls below', values: { thicknessLabel } })
124
154
  : raised
125
- ? `Slab ${thicknessLabel} thick on a ${foundationLabel} foundation, floor top ${topLabel ?? ''} above the ground`
126
- : `Slab ${thicknessLabel} thick on the ground, floor top ${topLabel ?? ''} above the ground`
155
+ ? i18n._({
156
+ id: 'editor.panels.floorFoundationPanel.sectionRaised',
157
+ message:
158
+ 'Slab {thickness} thick on a {foundation} foundation, floor top {top} above the ground',
159
+ values: { thickness: thicknessLabel, foundation: foundationLabel, top: topLabel ?? '' },
160
+ })
161
+ : i18n._({
162
+ id: 'editor.panels.floorFoundationPanel.sectionOnGround',
163
+ message: 'Slab {thickness} thick on the ground, floor top {top} above the ground',
164
+ values: { thickness: thicknessLabel, top: topLabel ?? '' },
165
+ })
127
166
  }
128
167
  className="h-auto w-full text-muted-foreground"
129
168
  role="img"
@@ -259,7 +298,7 @@ export function FloorSection({
259
298
  ))}
260
299
  </svg>
261
300
  <figcaption className="text-right text-[10px] text-muted-foreground/70">
262
- Not to scale
301
+ {i18n._({ id: 'editor.panels.floorFoundationPanel.notToScale', message: 'Not to scale' })}
263
302
  </figcaption>
264
303
  </figure>
265
304
  )
@@ -271,15 +310,29 @@ type Notice = {
271
310
  together: { ids: string[]; patch: FloorFoundationPatch; lower: boolean } | null
272
311
  }
273
312
 
274
- export function moveTogetherLabel(count: number, lower: boolean) {
275
- const verb = lower ? 'Lower' : 'Raise'
276
- return count === 2 ? `${verb} both` : `${verb} all ${count}`
313
+ export function moveTogetherLabel(count: number, lower: boolean, i18n: PascalI18n) {
314
+ const values = { count, number: String(count) }
315
+ return lower
316
+ ? i18n._({
317
+ id: 'editor.panels.floorFoundationPanel.lowerTogether',
318
+ message: '{count, plural, =2 {Lower both} other {Lower all {number}}}',
319
+ values,
320
+ })
321
+ : i18n._({
322
+ id: 'editor.panels.floorFoundationPanel.raiseTogether',
323
+ message: '{count, plural, =2 {Raise both} other {Raise all {number}}}',
324
+ values,
325
+ })
277
326
  }
278
327
 
279
328
  /** "Floor plate · Ground floor", upstairs "Floor plate · Floor 1 · rests on Ground floor". */
280
- export function floorPlateKicker(nodes: Record<string, AnyNode>, plate: SlabNode): string {
329
+ export function floorPlateKicker(
330
+ nodes: Record<string, AnyNode>,
331
+ plate: SlabNode,
332
+ i18n: PascalI18n,
333
+ locale: string,
334
+ ): string {
281
335
  const level = plate.parentId ? nodes[plate.parentId] : undefined
282
- const levelName = level?.type === 'level' ? ` · ${getLevelDisplayName(level)}` : ''
283
336
  // A supported floor rests on the storey right below it.
284
337
  let below: LevelNode | null = null
285
338
  if (level?.type === 'level' && floorFootprintSupportClass(nodes, plate) === 'supported')
@@ -291,8 +344,20 @@ export function floorPlateKicker(nodes: Record<string, AnyNode>, plate: SlabNode
291
344
  (!below || node.level > below.level)
292
345
  )
293
346
  below = node
294
- const restsOn = below ? ` · rests on ${getLevelDisplayName(below)}` : ''
295
- return `Floor plate${levelName}${restsOn}`
347
+ if (level?.type !== 'level')
348
+ return i18n._({ id: 'editor.panels.floorFoundationPanel.kicker', message: 'Floor plate' })
349
+ const levelName = localizedLevelName(level, i18n, locale)
350
+ return below
351
+ ? i18n._({
352
+ id: 'editor.panels.floorFoundationPanel.kickerRestsOn',
353
+ message: 'Floor plate · {level} · rests on {below}',
354
+ values: { level: levelName, below: localizedLevelName(below, i18n, locale) },
355
+ })
356
+ : i18n._({
357
+ id: 'editor.panels.floorFoundationPanel.kickerLevel',
358
+ message: 'Floor plate · {level}',
359
+ values: { level: levelName },
360
+ })
296
361
  }
297
362
 
298
363
  /**
@@ -311,7 +376,6 @@ export function floorFoundationModel(nodes: Record<string, AnyNode>, plate: Slab
311
376
  : 0
312
377
  return {
313
378
  upper,
314
- kicker: floorPlateKicker(nodes, plate),
315
379
  preset: upper ? ('ground' as const) : footprintPreset(nodes, plate),
316
380
  /** Upstairs: the floor top (level-local). On the ground: the foundation height. */
317
381
  height,
@@ -332,6 +396,8 @@ export function floorFoundationModel(nodes: Record<string, AnyNode>, plate: Slab
332
396
  * the building live and lands as one undo step.
333
397
  */
334
398
  export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClose: () => void }) {
399
+ const i18n = usePascalI18n()
400
+ const locale = useLocale()
335
401
  const nodes = useScene((state) => state.nodes)
336
402
  const unit = useViewer((s) => s.unit)
337
403
  const metricNotation = useViewer((s) => s.metricNotation)
@@ -341,7 +407,8 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
341
407
  const plate = nodes[node.id as AnyNodeId]
342
408
  if (plate?.type !== 'slab' || plate.plateRole !== 'base') return null
343
409
  const length = (meters: number) => formatLinearMeasurement(meters, unit, metricNotation)
344
- const { upper, kicker, preset, height, floorTop, minHeight, minThickness, underFloor, thickFloor } =
410
+ const kicker = floorPlateKicker(nodes, plate, i18n, locale)
411
+ const { upper, preset, height, floorTop, minHeight, minThickness, underFloor, thickFloor } =
345
412
  floorFoundationModel(nodes, plate)
346
413
  const level = plate.parentId ? nodes[plate.parentId as AnyNodeId] : undefined
347
414
  const maxHeight = level?.type === 'level' ? getStoredLevelHeight(level) - 0.5 : 3
@@ -389,7 +456,7 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
389
456
  icon="/icons/floor.webp"
390
457
  kicker={kicker}
391
458
  onClose={onClose}
392
- title={floorFootprintName(nodes, plate)}
459
+ title={localizedFloorFootprintName(nodes, plate, i18n)}
393
460
  >
394
461
  <div
395
462
  className="flex flex-col gap-3 px-4 pt-3 pb-4 text-sm"
@@ -407,13 +474,13 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
407
474
  />
408
475
  <section className="flex flex-col gap-1.5" data-floor-plate-group>
409
476
  <h3 className="font-medium text-muted-foreground text-xs">
410
- {upper ? 'Floor plate' : 'Slab'}
477
+ {upper ? i18n._({ id: 'editor.panels.floorFoundationPanel.floorPlate', message: 'Floor plate' }) : i18n._({ id: 'editor.panels.floorFoundationPanel.slab', message: 'Slab' })}
411
478
  </h3>
412
479
  {upper ? (
413
- <div title="A thicker floor grows upward: the rooms below keep their height.">
480
+ <div title={i18n._({ id: 'editor.panels.floorFoundationPanel.aThickerFloorGrowsUpwardThe', message: 'A thicker floor grows upward: the rooms below keep their height.' })}>
414
481
  <MetricControl
415
482
  className="h-9 text-xs"
416
- label="Floor thickness"
483
+ label={i18n._({ id: 'editor.panels.floorFoundationPanel.floorThickness', message: 'Floor thickness' })}
417
484
  max={Math.max(MIN_SLAB_THICKNESS, maxHeight - underside)}
418
485
  min={Math.max(MIN_SLAB_THICKNESS, minHeight - underside)}
419
486
  onChange={(next) => previewHeight(underside + next)}
@@ -426,10 +493,10 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
426
493
  />
427
494
  </div>
428
495
  ) : (
429
- <div title="The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it.">
496
+ <div title={i18n._({ id: 'editor.panels.floorFoundationPanel.theSlabSitsOnTheFoundation', message: 'The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it.' })}>
430
497
  <MetricControl
431
498
  className="h-9 text-xs"
432
- label="Slab thickness"
499
+ label={i18n._({ id: 'editor.panels.floorFoundationPanel.slabThickness', message: 'Slab thickness' })}
433
500
  max={1}
434
501
  min={minThickness}
435
502
  onChange={(next) => {
@@ -454,15 +521,19 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
454
521
  )}
455
522
  {upper && (
456
523
  <p className="px-0.5 text-muted-foreground text-xs" data-floor-top>
457
- Floor top +{length(floorTop)}
524
+ {i18n._({
525
+ id: 'editor.panels.floorFoundationPanel.floorTopAbove',
526
+ message: 'Floor top +{height}',
527
+ values: { height: length(floorTop) },
528
+ })}
458
529
  </p>
459
530
  )}
460
531
  <FinishSwatch
461
532
  fallback={EDGE_DEFAULT}
462
- hint="The floor's outside edge, all around the footprint."
463
- label="Edge finish"
533
+ hint={i18n._({ id: 'editor.panels.floorFoundationPanel.theFloorSOutsideEdgeAll', message: 'The floor\'s outside edge, all around the footprint.' })}
534
+ label={i18n._({ id: 'editor.panels.floorFoundationPanel.edgeFinish', message: 'Edge finish' })}
464
535
  node={plate}
465
- role="edge"
536
+ paintRole="edge"
466
537
  value={plate.slots?.edge}
467
538
  />
468
539
  {thickFloor && (
@@ -473,7 +544,7 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
473
544
  </section>
474
545
  {underFloor && (
475
546
  <section className="flex flex-col gap-1.5" data-under-floor-group>
476
- <h3 className="font-medium text-muted-foreground text-xs">Under the floor</h3>
547
+ <h3 className="font-medium text-muted-foreground text-xs">{i18n._({ id: 'editor.panels.floorFoundationPanel.underTheFloor', message: 'Under the floor' })}</h3>
477
548
  <SegmentedControl
478
549
  onChange={(next: FootprintPreset) => {
479
550
  if (next === preset) return
@@ -481,17 +552,17 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
481
552
  report(applyFloorFoundation(plate.id, patch), patch)
482
553
  }}
483
554
  options={[
484
- { label: 'On the ground', value: 'ground' },
485
- { label: 'Raised on a foundation', value: 'raised' },
555
+ { label: i18n._({ id: 'editor.panels.floorFoundationPanel.onTheGround', message: 'On the ground' }), value: 'ground' },
556
+ { label: i18n._({ id: 'editor.panels.floorFoundationPanel.raisedOnAFoundation', message: 'Raised on a foundation' }), value: 'raised' },
486
557
  ]}
487
558
  value={preset}
488
559
  />
489
560
  {preset === 'raised' && (
490
561
  <>
491
- <div title="The foundation stands on the ground; the slab sits on it.">
562
+ <div title={i18n._({ id: 'editor.panels.floorFoundationPanel.theFoundationStandsOnTheGround', message: 'The foundation stands on the ground; the slab sits on it.' })}>
492
563
  <MetricControl
493
564
  className="h-9 text-xs"
494
- label="Foundation height"
565
+ label={i18n._({ id: 'editor.panels.floorFoundationPanel.foundationHeight', message: 'Foundation height' })}
495
566
  max={Math.max(0.05, maxHeight)}
496
567
  min={minHeight}
497
568
  onChange={previewHeight}
@@ -505,9 +576,9 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
505
576
  </div>
506
577
  <FinishSwatch
507
578
  fallback={FOUNDATION_DEFAULT}
508
- label="Foundation finish"
579
+ label={i18n._({ id: 'editor.panels.floorFoundationPanel.foundationFinish', message: 'Foundation finish' })}
509
580
  node={plate}
510
- role="foundation"
581
+ paintRole="foundation"
511
582
  value={
512
583
  typeof plate.foundation?.material === 'string'
513
584
  ? plate.foundation.material
@@ -517,7 +588,11 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
517
588
  </>
518
589
  )}
519
590
  <p className="px-0.5 text-muted-foreground text-xs" data-floor-top>
520
- Floor top {length(floorTop)}
591
+ {i18n._({
592
+ id: 'editor.panels.floorFoundationPanel.floorTopValue',
593
+ message: 'Floor top {height}',
594
+ values: { height: length(floorTop) },
595
+ })}
521
596
  </p>
522
597
  </section>
523
598
  )}
@@ -540,7 +615,7 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
540
615
  type="button"
541
616
  variant="outline"
542
617
  >
543
- {moveTogetherLabel(notice.together.ids.length, notice.together.lower)}
618
+ {moveTogetherLabel(notice.together.ids.length, notice.together.lower, i18n)}
544
619
  </Button>
545
620
  )}
546
621
  </div>